Keen的博客

记录所思、所想、所遇

欢迎来到我的个人站~


【调试技术】Windows问题Debug

1. 符号表

最近微软貌似说将符号表服务器迁移到了Azure国际版,国内被墙了,符号下载不了,找了一番,在看雪论坛找到了这个替代品:http://sym.ax2401.com:9999/symbols/,感谢大神分享!!!!!!

2. Windbg常用命令

2.1. 应用层调试命令

# 查看模块列表,及模块信息,一般用来看pdb加载情况
lm
lmvm xxx.dll
!lmi xxx

# 强制加载不匹配符号。对于外发包经常由于时间戳或者签名不一致,虽然代码一致,但是也无法正常加载pdb,可以使用此方法加载
.reload /f /i xxxx.dll

# 启用不完全匹配SYMOPT_LOAD_ANYTHING
.symopt +0x40

# 查看符号,一般用来辅助bp下断点
x qtcore!*test*

# 下断点,bp对函数下断点,bl查看断点列表及id,bc则通过bl查看到的id,进行删除断点
bp qtcore!xxxxxx
bl
bc 1

# 查看调用栈
kv

# 查看所有线程调用栈
~* kv

# 查看局部变量及地址
dv /v /t

# 有时候局部变量不准确,是因为优化导致的,可以尝试切换帧
.frame /c n

# 查看某个句柄信息
!handle 0x002345 f

# 查看异常,其中,0x002345表示ExceptionRecord地址,0x002349表示ContextRecord地址
.exr 0x002345
.cxr 0x002349

# 查看结构体(结构体名一般可以使用Local视图查看),可以用来看某个成员的偏移
dt 0x26665440 xxxstruct

# 计算表达式,也可以用来转换整数0nxxx到十六进制
? 0x26665440 +0x280

# 编辑内存,eb按照一个字节编辑、ew按照两个字节编辑、ed按照4字节编辑、ef和eD分别编辑浮点型、eza编辑null结尾的ascii字符串、ezu编辑null结尾unicode字符串、ea和eu则分别编辑非null结尾字符串
eb 0x26665448 1

# 条件断点,查看loadlibrary加载某个dll时中断(createfile等其他api类似)
ad dllname
bp Kernelbase!loadlibraryexw "as /mu ${/v:dllname} poi(@esp+4); .block{.if($spat(@\"${dllname}\",@\"*test.dll\")) {.echo ${dllname};kb;} .else {gc;}}"

2.2. 内核层调试命令

# 查看进程列表
!process 0 0

# 根据进程地址,查看调用栈
!process 0x002345 7

# 一键查看所有进程的所有线程调用栈(内核线程上千条,不太建议)
!process 0 7

3. 问题1:卸载Visual Studio 2015卡死

最近安装了vs2019,对我这老机器有限的磁盘空间而言,vs2015占据7.5G,为此一直耿耿于怀,而vs2019本身对2015~2019编译器有插件集成,工程编译没有问题,我就不需要2015额外的空间占用了。都知道,对于vs2015,因为安装后,默认安装了一堆Tools、.Net版本等,想要尽可能完全的卸载所有相关的依赖组件,就最好使用vs2015的vs_community.exe集成的卸载器功能(vs2019比较好的一点,是增加了一个visual studio installer工具,将组件安装卸载都集成在此,并随安装包一起安装在本地,无需单独去寻找离线包),为此找到vs2015的离线包iso,加载到虚拟光驱,运行,开始了卸载历程。

然而,没过多久,就一直卡在这个进度:

PNG

很明显,卸载进程出现卡死。

3.1. 首先找到是哪个卸载进程卡死

这里使用工具ProcessExp,首先找到当下这个卸载界面是哪个进程,查找到如下:

PNG

需要打开ProcessExp的进程树,因为我们得找到具体是哪个进程卡死,可能是其中的子进程。通过进程树信息可以看见,实际卸载过程,是由众多进程参与的,我们首先查看最下面的子进程,因为按常规来看,是最终拉起的进程干活。抓minidump先分析一下,打开windbg调试器,设置好符号表,打开dmp文件,开始分析。

首先,查看所有线程的栈信息,其他线程栈看起来都比较正常,最有怀疑的是0号线程

0:000> ~* kv

.  0  Id: 1944.2ac8 Suspend: 0 Teb: 00c06000 Unfrozen
 # ChildEBP RetAddr  Args to Child              
00 00f1f104 77c9e78a 759f231a 0000024c 00000000 ntdll!KiFastSystemCallRet (FPO: [0,0,0])
01 00f1f108 759f231a 0000024c 00000000 00000000 ntdll!NtReadFile+0xa (FPO: [9,0,0])
DBGHELP: e:/symbols*http://sym.ax2401.com:9999/symbols/ is not a valid store
*** ERROR: Module load completed but symbols could not be loaded for vssdk_full.exe
02 00f1f16c 0054306c 0000024c 00f1f194 00000008 KERNELBASE!ReadFile+0x7a (FPO: [Non-Fpo])
WARNING: Stack unwind information not available. Following frames may be wrong.
03 00f1f1a4 005434f0 0000024c 00f1f200 00000000 vssdk_full+0x306c
04 00f1f1d8 00543b0c 0000024c 0054ebde 00f1f230 vssdk_full+0x34f0
05 00f1f200 0054fe90 0000024c 0000000c 0b278e50 vssdk_full+0x3b0c
DBGHELP: e:/symbols*http://sym.ax2401.com:9999/symbols/ is not a valid store
06 00f1f244 0056311c 0000024c 00000000 0b24db48 vssdk_full+0xfe90
07 00f1f27c 00563c71 00000000 00f1f2ac 00f1f35c vssdk_full+0x2311c
08 00f1f2b4 00564097 00f1f3e4 00000000 00f1f2fc vssdk_full+0x23c71
09 00f1f30c 0054c713 00000000 00000000 000000f0 vssdk_full+0x24097
0a 00f1f36c 00541303 00000000 00000000 0054180f vssdk_full+0xc713
DBGHELP: e:/symbols*http://sym.ax2401.com:9999/symbols/ is not a valid store
0b 00f1f3b0 00541a67 00f1f3d4 00000000 00000000 vssdk_full+0x1303
0c 00f1f3cc 00541e34 00000000 00568732 00000000 vssdk_full+0x1a67
DBGHELP: e:/symbols*http://sym.ax2401.com:9999/symbols/ is not a valid store
0d 00f1fa0c 00541028 00540000 010b3310 0000000a vssdk_full+0x1e34
0e 00f1fa28 005686df 00540000 00000000 010b3310 vssdk_full+0x1028
0f 00f1fab8 77152369 00c05000 77152350 00f1fb24 vssdk_full+0x286df
10 00f1fac8 77c6e5bb 00c05000 20921238 00000000 kernel32!BaseThreadInitThunk+0x19 (FPO: [Non-Fpo])
11 00f1fb24 77c6e58f ffffffff 77cb3e63 00000000 ntdll!__RtlUserThreadStart+0x2b (FPO: [Non-Fpo])
12 00f1fb34 00000000 00568732 00c05000 00000000 ntdll!_RtlUserThreadStart+0x1b (FPO: [Non-Fpo])

从栈信息可以看到:线程正在等待ReadFile返回结果
ReadFile卡死,就可以YY出很多可能性,是读某个过大文件?文件系统异常? 不确定ReadFile的对象,就无法明确它出现的问题。ReadFile第一个参数,是读取对象的HANDLE,如果是文件,用windbg是可以找到文件或注册表路径的,也可以分析出handle的具体类型。

png

可以看出,这个ReadFile是在等某个管道的输入,那么猜测只能是跟父进程在通信等待某个结果完成,一直等不到就一直卡死。因此,还需要继续看一下父进程信息,继续用ProcessExp抓取父进程winsdk_full.exe的minidump。

png

其他线程看起来比较正常,就0号线程,发现有一个DialogBox?很奇怪,这种模式对话框不应该show出windows出来么?这直接卡死在这个线程出不去了,也就是,卡死肯定是它造成的。

问题比较容易发现,怎么解决呢?有很多种方式,比如找到这个窗口句柄,然后Post一个关闭消息,将窗口关闭掉,或者尝试修改窗口隐藏属性,将窗口显示出来。

3.2. 找到是哪个窗口

通过windbg分析栈去查找窗口比较麻烦,我们知道,DialogBox一定包含对话框窗口类属性,同时,通过上述栈信息,我们知道,0号线程的pid是2a2c,tid是1d9c,我们可以通过spy++去抓取进程的线程相关的所有窗口,就可以将窗口缩小到最小范围。

png

这里边,比较容易找到的是窗口句柄为0270d14,它包含对话框属性。剩下比较意外的收获是可以看见一个Static控件上,写有一个相当熟悉的文案:查找某个msi失败了。通过历史经验,我们知道,这里一定是卸载时某些组件升级和版本不兼容导致卸载组件异常,而卡死流程,而vs出了这么个bug,无法展示这个对话框给我们。
为了进一步确认窗口句柄为0270d14就是我们最终找到的窗口,Static控件是它的子窗口,spy++转到窗口层级就可以很明显了。选中窗口,点击右键菜单-属性,点击同步,即可转到窗口树形层级结构图上去看。

png

3.3. 关闭对话框窗口

关闭窗口,可以发送WM_CLOSE,但是我们发现还有一个OK控件和一个Cancel控件,对于这类错误弹框,如果简单的close主窗口,会重复弹出。根据文案描述,这里我们需要去点击Cancel取消这个流程。我们可以写代码去模拟点击,这里我比较懒,想到另一个工具AccExplorer.exe,可以直接模拟点击(因为控件是窗口,所以这个工具可以正常工作,对于自绘窗口控件是不行的,这个工具有这个局限性)。
选择Option–>Choose Window List,选择目标窗口(这里前面已经找到目标窗口的标题属性,如果有重复,可以一个个去确认尝试)

png png

找到了Cancel控件,直接模拟点击即可。至此,流程发现已经往下继续走了。后续发现又有重复类型的问题,可以简单通过spy++和accexplorer来解决卡死问题。

3.4. 解决visual studio 2015卸载问题

前面的方法,可以找到为何卡死,以及如何解决卡死的一种方法。实际上,当分析到进程树时,当我们观察卸载过程,就会发现,它卸载也是调用不同进程。前面的方法,即使解决了弹框卡死,卸载流程实际是失败的,那么我们让当前插件的卸载进程继续执行已经没有意义了,此时,解决卡死问题最简单的方式,就是直接杀掉卡死组件的卸载进程,只让部分组件无法卸载。

然而不幸的是,我机器上C:\ProgramData\PackageCache中的文件被某个不良工具清除掉了,所以导致大面积卸载异常,最后卸载失败。无奈尝试使用了微软官方的一系列卸载方法:

对于方法一和方法二,一般可以优先尝试,因为方法三非常耗时。然而,由于我的机器大面积异常,尝试前两种方法对我无效。方法三的基本使用方式如下:

  • Step1:控制面板执行visual studio 2015卸载,等待卡死
  • Step2:分析卡死的窗口,spy++查看对应窗口的msi组件文案,确认是哪个msi
  • Step3:使用下载下来的工具MicrosoftProgram_Install_and_Uninstall.meta.diagcab,选择卸载,然后从Step2我们知道具体是哪个msi,从扫描到的列表中,选择该卸载异常的msi,执行卸载
  • Step4:重复执行Step1,等待卡死,直到无任何卡死

4. 问题2:定位测试程序卡死

测试程序D3D12Test.exe,启动后卡死。使用Windbg Attach上去调试:

0:009> ~* kv

   0  Id: 543c.3968 Suspend: 1 Teb: 00219000 Unfrozen
 # ChildEBP RetAddr  Args to Child              
00 0055406c 770692be 005540e4 76f49482 00081cd8 win32u!NtUserDispatchMessage+0xc (FPO: [1,0,0])
01 005540c0 77068cf0 76a1d442 00554110 77086486 USER32!DispatchMessageWorker+0x5be (FPO: [Non-Fpo])
02 005540cc 77086486 005540e4 00081cd8 00000001 USER32!DispatchMessageW+0x10 (FPO: [Non-Fpo])
03 00554110 770869ea 00000000 00000001 00000000 USER32!DialogBox2+0x170 (FPO: [Non-Fpo])
04 00554140 770c089b 00081cd8 770a1bf0 00554390 USER32!InternalDialogBox+0xef (FPO: [4,5,4])
05 00554214 770a2523 00554390 00554518 00000000 USER32!SoftModalMessageBox+0x72b (FPO: [1,43,4])
06 00554378 770c0115 013a145b 00000000 0055e988 USER32!MessageBoxWorker+0x29a (FPO: [Non-Fpo])
07 00554400 770c015a 00081cd8 00554518 0fc3af50 USER32!MessageBoxTimeoutW+0x165 (FPO: [6,29,4])
08 00554420 0fcc1618 00081cd8 00554518 0fc3af50 USER32!MessageBoxW+0x1a (FPO: [Non-Fpo])
09 00554440 0fccbe32 00081cd8 00554518 0fc3af50 ucrtbased!__acrt_MessageBoxW+0x38 (FPO: [Non-Fpo]) (CONV: stdcall) [minkernel\crts\ucrt\src\appcrt\internal\winapi_thunks.cpp @ 696] 
0a 00554458 0fccbdd9 00554470 00554490 00554494 ucrtbased!__crt_char_traits<wchar_t>::message_box<HWND__ *,wchar_t const * const &,wchar_t const * const &,unsigned int const &>+0x22 (FPO: [Non-Fpo]) (CONV: cdecl) [minkernel\crts\ucrt\inc\corecrt_internal_traits.h @ 124] 
0b 00554488 0fccbeb6 00554518 0fc3af50 00012012 ucrtbased!common_show_message_box<wchar_t>+0x109 (FPO: [Non-Fpo]) (CONV: cdecl) [minkernel\crts\ucrt\src\appcrt\misc\crtmbox.cpp @ 75] 
0c 0055449c 0fccca00 00554518 0fc3af50 00012012 ucrtbased!__acrt_show_wide_message_box+0x16 (FPO: [Non-Fpo]) (CONV: cdecl) [minkernel\crts\ucrt\src\appcrt\misc\crtmbox.cpp @ 93] 
0d 00556728 0fccd3b2 00000001 0fcbfe4c 00000000 ucrtbased!common_message_window<wchar_t>+0x4a0 (FPO: [Non-Fpo]) (CONV: cdecl) [minkernel\crts\ucrt\src\appcrt\misc\dbgrpt.cpp @ 409] 
0e 00556748 0fcce8d4 00000001 0fcbfe4c 00000000 ucrtbased!__acrt_MessageWindowW+0x22 (FPO: [Non-Fpo]) (CONV: cdecl) [minkernel\crts\ucrt\src\appcrt\misc\dbgrpt.cpp @ 464] 
0f 0055e800 0fccd2ff 00000001 0fcbfe4c 00000000 ucrtbased!_VCrtDbgReportW+0x964 (FPO: [Non-Fpo]) (CONV: cdecl) [minkernel\crts\ucrt\src\appcrt\misc\dbgrptt.cpp @ 673] 
10 0055e82c 0fcbfe4c 00000001 00000000 00000000 ucrtbased!_CrtDbgReportW+0x2f (FPO: [Non-Fpo]) (CONV: cdecl) [minkernel\crts\ucrt\src\appcrt\misc\dbgrpt.cpp @ 278] 
11 0055e850 0fcbfff1 0fc43524 0071fb78 0055e890 ucrtbased!issue_debug_notification+0x1c (FPO: [Non-Fpo]) (CONV: cdecl) [minkernel\crts\ucrt\src\appcrt\internal\report_runtime_error.cpp @ 25] 
12 0055e868 0fcd12ca 0fc43524 6ff6335c 0055e8b0 ucrtbased!__acrt_report_runtime_error+0x11 (FPO: [Non-Fpo]) (CONV: cdecl) [minkernel\crts\ucrt\src\appcrt\internal\report_runtime_error.cpp @ 154] 
13 0055e878 0fcd076d 4a98ddad 013a145b 00000000 ucrtbased!abort+0x1a (FPO: [Non-Fpo]) (CONV: cdecl) [minkernel\crts\ucrt\src\appcrt\startup\abort.cpp @ 51] 
14 0055e8b0 013b3725 0055eac8 0055e958 7792de80 ucrtbased!terminate+0x7d (FPO: [Non-Fpo]) (CONV: cdecl) [minkernel\crts\ucrt\src\appcrt\misc\terminate.cpp @ 59] 
15 0055e8bc 7792de80 0055e988 abad65ee 00000000 D3D12Test!__scrt_unhandled_exception_filter+0x55 (FPO: [Non-Fpo]) (CONV: stdcall) [d:\agent\_work\3\s\src\vctools\crt\vcstartup\src\utility\utility_desktop.cpp @ 94] 
16 0055e958 77f032b2 0055e988 77ed3cf2 0055fc9c KERNELBASE!UnhandledExceptionFilter+0x1a0 (FPO: [Non-Fpo])
17 0055fcac 77ec5e97 ffffffff 77eeae74 00000000 ntdll!__RtlUserThreadStart+0x3d41a
18 0055fcbc 00000000 013a1a7d 00216000 00000000 ntdll!_RtlUserThreadStart+0x1b (FPO: [Non-Fpo])

可以知道是有一个MessageBox弹框出现导致0号线程卡死,而出现这个弹框,是因为一个异常出现。
第16号栈帧可以看到捕获到异常KERNELBASE!UnhandledExceptionFilter,它的参数如下:

LONG WINAPI UnhandledExceptionFilter(
  _In_ struct _EXCEPTION_POINTERS *ExceptionInfo
);

typedef struct _EXCEPTION_POINTERS {
  PEXCEPTION_RECORD ExceptionRecord;
  PCONTEXT          ContextRecord;
} EXCEPTION_POINTERS, *PEXCEPTION_POINTERS;

直接查看结构,可以看到异常基本信息:

# 取KERNELBASE!UnhandledExceptionFilter第一个参数:0055e988
0:009> dt /r EXCEPTION_POINTERS 0055e988 
hookd3d9!EXCEPTION_POINTERS
   +0x000 ExceptionRecord  : 0x0055eac8 _EXCEPTION_RECORD
      +0x000 ExceptionCode    : 0xe06d7363
      +0x004 ExceptionFlags   : 1
      +0x008 ExceptionRecord  : (null) 
      +0x00c ExceptionAddress : 0x778a4bb2 Void
      +0x010 NumberParameters : 3
      +0x014 ExceptionInformation : [15] 0x19930520
   +0x004 ContextRecord    : 0x0055eb18 _CONTEXT
      +0x000 ContextFlags     : 0x1007f
      +0x004 Dr0              : 0
      +0x008 Dr1              : 0
      +0x00c Dr2              : 0
      +0x010 Dr3              : 0
      +0x014 Dr6              : 0
      +0x018 Dr7              : 0
      +0x01c FloatSave        : _FLOATING_SAVE_AREA
         +0x000 ControlWord      : 0x27f
         +0x004 StatusWord       : 0x120
         +0x008 TagWord          : 0xffff
         +0x00c ErrorOffset      : 0xfb443a1
         +0x010 ErrorSelector    : 0
         +0x014 DataOffset       : 0
         +0x018 DataSelector     : 0
         +0x01c RegisterArea     : [80]  ""
         +0x06c Spare0           : 0
      +0x08c SegGs            : 0x2b
      +0x090 SegFs            : 0x53
      +0x094 SegEs            : 0x2b
      +0x098 SegDs            : 0x2b
      +0x09c Edi              : 0x55f090
      +0x0a0 Esi              : 0xf2615a8
      +0x0a4 Ebx              : 0x216000
      +0x0a8 Edx              : 0
      +0x0ac Ecx              : 3
      +0x0b0 Eax              : 0x55eff8
      +0x0b4 Ebp              : 0x55f050
      +0x0b8 Eip              : 0x778a4bb2
      +0x0bc SegCs            : 0x23
      +0x0c0 EFlags           : 0x212
      +0x0c4 Esp              : 0x55eff8
      +0x0c8 SegSs            : 0x2b
      +0x0cc ExtendedRegisters : [512]  "???"

# .exr和.cxr是分别查看ExceptionRecord和ContextRecord的命令,同时,会将栈切换到异常时上下文
0:009> .exr 0x0055eac8 
ExceptionAddress: 778a4bb2 (KERNELBASE!RaiseException+0x00000062)
   ExceptionCode: e06d7363 (C++ EH exception)
  ExceptionFlags: 00000001
NumberParameters: 3
   Parameter[0]: 19930520
   Parameter[1]: 0055f0c4
   Parameter[2]: 013bd278
  pExceptionObject: 0055f0c4
  _s_ThrowInfo    : 013bd278
  Type            : class HrException
  Type            : class std::runtime_error
  Type            : class std::exception
0:009> .cxr 0x0055eb18 
eax=0055eff8 ebx=00216000 ecx=00000003 edx=00000000 esi=0f2615a8 edi=0055f090
eip=778a4bb2 esp=0055eff8 ebp=0055f050 iopl=0         nv up ei pl nz ac po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000212
KERNELBASE!RaiseException+0x62:
778a4bb2 8b4c2454        mov     ecx,dword ptr [esp+54h] ss:002b:0055f04c=abad634e

# 当前已经切换到异常上下文,直接查看调用栈信息
0:000> kv
  *** Stack trace for last set context - .thread/.cxr resets it
 # ChildEBP RetAddr  Args to Child              
00 0055f050 0f2697a9 e06d7363 00000001 00000003 KERNELBASE!RaiseException+0x62 (FPO: [4,22,0])
01 0055f0a4 013ae34e 0055f0c4 013bd278 0055f198 VCRUNTIME140D!_CxxThrowException+0xa9 (FPO: [Non-Fpo]) (CONV: stdcall) [d:\agent\_work\1\s\src\vctools\crt\vcruntime\src\eh\throw.cpp @ 133] 
02 0055f198 013abbcd 80070003 aff51556 0055fac0 D3D12Test!ThrowIfFailed+0x4e (FPO: [Non-Fpo]) (CONV: cdecl) [d:\keencode\d3dsample\d3d12test\d3drender.cpp @ 61] 
03 0055f9ec 013aabb9 0055fbcc 0055facc 00216000 D3D12Test!LoadAssets+0x1dd (FPO: [Non-Fpo]) (CONV: cdecl) [d:\keencode\d3dsample\d3d12test\d3drender.cpp @ 196] 
04 0055fac0 013a49ac 00081cd8 013a1a7d 013a1a7d D3D12Test!InitDevice+0x39 (FPO: [Non-Fpo]) (CONV: cdecl) [d:\keencode\d3dsample\d3d12test\d3drender.cpp @ 302] 
05 0055fbcc 013b1f4e 01390000 00000000 00702708 D3D12Test!wWinMain+0x9c (FPO: [Non-Fpo]) (CONV: stdcall) [d:\keencode\d3dsample\d3d12test\d3d12test.cpp @ 44] 
06 0055fbe4 013b1db7 aff510fa 013a1a7d 013a1a7d D3D12Test!invoke_main+0x1e (FPO: [Non-Fpo]) (CONV: cdecl) [d:\agent\_work\3\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl @ 123] 
07 0055fc40 013b1c4d 0055fc50 013b1fc8 0055fc64 D3D12Test!__scrt_common_main_seh+0x157 (FPO: [Non-Fpo]) (CONV: cdecl) [d:\agent\_work\3\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl @ 288] 
08 0055fc48 013b1fc8 0055fc64 77cc8674 00216000 D3D12Test!__scrt_common_main+0xd (FPO: [Non-Fpo]) (CONV: cdecl) [d:\agent\_work\3\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl @ 331] 
09 0055fc50 77cc8674 00216000 77cc8650 ab8016bc D3D12Test!wWinMainCRTStartup+0x8 (FPO: [Non-Fpo]) (CONV: cdecl) [d:\agent\_work\3\s\src\vctools\crt\vcstartup\src\startup\exe_wwinmain.cpp @ 17] 
0a 0055fc64 77ec5ec7 00216000 6fa3cff0 00000000 KERNEL32!BaseThreadInitThunk+0x24 (FPO: [Non-Fpo])
0b 0055fcac 77ec5e97 ffffffff 77eeae74 00000000 ntdll!__RtlUserThreadStart+0x2f (FPO: [SEH])
0c 0055fcbc 00000000 013a1a7d 00216000 00000000 ntdll!_RtlUserThreadStart+0x1b (FPO: [Non-Fpo])

找到异常内容了。

5. 问题3:定位内存泄漏

5.1 场景1:能稳定复现

5.1.1 有代码

尝试使用vs的性能分析器 - 内存指标,去分析,抓snapshot快照方式,对比前后差异,按照增量大小排序,未释放的即为内存泄漏点。也可以使用UMDH工具抓快照分析,具体参考网上教程。

5.1.2 无代码有pdb

使用UMDH工具gflags.exe,使用命令行:gflags.exe -i xxxxx.exe即可开启堆tag,进而尝试重现后,按照5.2的分析方法去分析。

5.2 场景2:只有一次全dump

5.2.1 Step1:查看堆信息,找出疑似泄漏堆(如分配最大者)

# 查看总共有哪些堆,以及各自堆大小,内存泄漏一般为其中最大堆(不一定,可以一一尝试)
0:000> !heap -s
SEGMENT HEAP ERROR: failed to initialize the extention
LFH Key                   : 0x4d5d83a8
Termination on corruption : DISABLED
  Heap     Flags   Reserv  Commit  Virt   Free  List   UCR  Virt  Lock  Fast 
                    (k)     (k)    (k)     (k) length      blocks cont. heap 
-----------------------------------------------------------------------------
Virtual block: 07dc0000 - 07dc0000 (size 00000000)
Virtual block: 07fe0000 - 07fe0000 (size 00000000)
Virtual block: 080f0000 - 080f0000 (size 00000000)
Virtual block: 08320000 - 08320000 (size 00000000)
Virtual block: 08430000 - 08430000 (size 00000000)
Virtual block: 08a10000 - 08a10000 (size 00000000)
Virtual block: 116c0000 - 116c0000 (size 00000000)
Virtual block: 0ee50000 - 0ee50000 (size 00000000)
Virtual block: 10e60000 - 10e60000 (size 00000000)
Virtual block: 10c20000 - 10c20000 (size 00000000)
004b0000 00000002    8192   6052   8192    502   381     4   10      5   LFH
00010000 00008000      64      4     64      2     1     1    0      0      
001a0000 00001002    1088    240   1088      3     3     2    0      0   LFH
00680000 00001002     256    148    256      4    10     1    0      0   LFH
00120000 00001002    1088    268   1088     18    28     2    0      0   LFH
01900000 00001002    1088    728   1088     48    57     2    0      0   LFH
01a10000 00001002      64      4     64      2     1     1    0      0      
00140000 00011002     256     20    256     17     2     1    0      0      
Virtual block: 07a70000 - 07a70000 (size 00000000)
00310000 00001002 1682164 1669420 1682164   7769   618   318    1     23   LFH
00300000 00001002      64     12     64      2     2     1    0      0      
01a80000 00001002     256      4    256      2     1     1    0      0      
029b0000 00001002    1088    184   1088     12     4     2    0      0   LFH
02ef0000 00001002      64     12     64      4     2     1    0      0      
03810000 00001002     256      8    256      3     1     1    0      0      
038d0000 00001002     256    152    256      1     5     1    0      0   LFH
04240000 00001002    1088    172   1088     11     7     2    0      0   LFH
07c40000 00001002    1088    176   1088      8     8     2    0      0   LFH
07410000 00001002    1088    168   1088     13     5     2    0      0   LFH
0a440000 00001002      64      4     64      2     1     1    0      0      
0ab50000 00001002      64      4     64      2     1     1    0      0      
0ad10000 00001002     256      4    256      1     2     1    0      0      
0dc20000 00001002     256     96    256      8     5     1    0      0   LFH
10a00000 00001002    1088    136   1088     20     5     2    0      0   LFH
11380000 00001002    1088    136   1088      7     6     2    0      0   LFH
Virtual block: 11390000 - 11390000 (size 00000000)
Virtual block: 09630000 - 09630000 (size 00000000)
Virtual block: 09c70000 - 09c70000 (size 00000000)
Virtual block: 124d0000 - 124d0000 (size 00000000)
Virtual block: 0ab60000 - 0ab60000 (size 00000000)
09d50000 00001002   31616  21064  31616   7362   175    13    5      1   LFH
    External fragmentation  34 % (175 free blocks)
11370000 00001002      64     32     64     30     1     1    0      0      
0f500000 00001002    1088    296   1088     25    20     2    0      0   LFH
10190000 00001002    1088    160   1088      3    13     2    0      0   LFH
0b640000 00001002     256    112    256      5     7     1    0      0   LFH
-----------------------------------------------------------------------------

可以看到,上述最大堆句柄:00310000。

5.2.2 Step2:查看堆块内存占比

# 根据堆句柄,分析堆大小分配占比
0:000> !heap -stat -h 00310000 
 heap @ 00310000
group-by: TOTSIZE max-display: 20
    size     #blocks     total     ( %) (percent of total busy bytes)
    10000 6228 - 62280000  (97.11)
    16c9 1879 - 22d9d01  (2.16)
    800 3e4 - 1f2000  (0.12)
    490 3cf - 116070  (0.07)
    449 3db - 108573  (0.06)
    100 a78 - a7800  (0.04)
    140 808 - a0a00  (0.04)
    11c 83c - 92290  (0.04)
    8c074 1 - 8c074  (0.03)
    c0 774 - 59700  (0.02)
    f4 591 - 54e34  (0.02)
    50 fa9 - 4e4d0  (0.02)
    41180 1 - 41180  (0.02)
    24 1c97 - 4053c  (0.02)
    bb88 5 - 3a9a8  (0.01)
    80 6cc - 36600  (0.01)
    32000 1 - 32000  (0.01)
    301bc 1 - 301bc  (0.01)
    2e400 1 - 2e400  (0.01)
    28050 1 - 28050  (0.01)

可以看到,上述0x10000大小的堆,占比最大,最有可能泄漏。

5.2.3 Step3:查看特定内存大小的所有堆首地址

# 查看10000大小的堆首地址UserPtr
0:000> !heap -flt s 10000
    _HEAP @ 4b0000
    _HEAP @ 10000
    _HEAP @ 1a0000
    _HEAP @ 680000
    _HEAP @ 120000
    _HEAP @ 1900000
    _HEAP @ 1a10000
    _HEAP @ 140000
    _HEAP @ 310000
      HEAP_ENTRY Size Prev Flags    UserPtr UserSize - state
        0c24afe0 2001 0000  [00]   0c24afe8    10000 - (busy)
          ? TAVE!std::_Init_locks::operator=+157e4
        0c25afe8 2001 2001  [00]   0c25aff0    10000 - (busy)
        13c90040 2001 2001  [00]   13c90048    10000 - (busy)
        13ca0048 2001 2001  [00]   13ca0050    10000 - (busy)
        13cb1720 2001 2001  [00]   13cb1728    10000 - (busy)
        13cc1728 2001 2001  [00]   13cc1730    10000 - (busy)
        13cd1730 2001 2001  [00]   13cd1738    10000 - (busy)
        13ce1738 2001 2001  [00]   13ce1740    10000 - (busy)
        13cf1740 2001 2001  [00]   13cf1748    10000 - (busy)
        13d01748 2001 2001  [00]   13d01750    10000 - (busy)
        13d11750 2001 2001  [00]   13d11758    10000 - (busy)
        13d21758 2001 2001  [00]   13d21760    10000 - (busy)
          shell32!__dyn_tls_init_callback <PERF> (shell32+0x708801)
        13d31760 2001 2001  [00]   13d31768    10000 - (busy)
        13d41768 2001 2001  [00]   13d41770    10000 - (busy)
        13d51770 2001 2001  [00]   13d51778    10000 - (busy)
        13d61778 2001 2001  [00]   13d61780    10000 - (busy)
        13d72e50 2001 2001  [00]   13d72e58    10000 - (busy)
        13d82e58 2001 2001  [00]   13d82e60    10000 - (busy)
        ......

这里有个技巧,就是可以看下这个堆,是否是结构体产生的,可以看一下堆的内存信息(dd xxxx),看下有无相似的出现,确认是否是对象泄漏。

5.2.4 Step4:尝试查找堆分配栈

# 这里只有在本地开启了堆tag前提下,才有效,不然没有符号分配信息
0:000> !heap -p -a 0c24afe8    
    address 0c24afe8 found in
    _HEAP @ 310000
      HEAP_ENTRY Size Prev Flags    UserPtr UserSize - state
        0c24afe0 2001 0000  [00]   0c24afe8    10000 - (busy)
          ? TAVE!std::_Init_locks::operator=+157e4

5.2.5 Step5:尝试Windbg调试查找堆大小分配栈

# 下内存断点,查找特定大小堆分配栈(不一定会触发分配点,不过值得一试,在一些特殊大小的场景下效率非常高)
bu ntdll!RtlAllocateHeap ".if (poi(esp+c)=10000) {kv} .else {g}"

5.3 场景3:无法定位无法复现

通常这里只有minidump的情况,这时需要尝试去收集全量dump。在收集全量dump的时候,为了更加有效去分析,可以尝试增加堆tag信息,方便直接定位问题。

打赏一个呗

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码支持
扫码打赏,你说多少就多少