内存泄漏
1、方法一:批处理 umdh
原理:借助winDBG 工具,先设置好pdb路径,然后加入进程,运行进程,抓取第一次快照(Snap1.log );运行一段时间(让内存增长),然后执行内存泄漏检测分析.bat,抓取第二次快照(Snap2.log)。最终,比较两次快照结果存到result.txt 中
step1:内存泄漏开始.bat
C:
cd C:\Program Files\Debugging Tools for Windows (x86)//windbg 安装路径
set _NT_SYMBOL_PATH=E:\testwy\Pdb //pdb 路径
gflags -i OPCServer.exe +ust //要加载的进程
umdh -pn:OPCServer.exe -f:d:\Snap1.log //快照路径
pause
step2:内存泄漏检测.bat
cd C:\Program Files\Debugging Tools for Windows (x86) //winDbg 路径
set _NT_SYMBOL_PATH=E:\testwy\Pdb //pdb 路径
umdh -pn:OPCServer.exe -f:d:\Snap2.log //第二次快照路径
umdh -d D:\Snap1.log D:\Snap2.log -f:d:\result.txt //比较两次快照的分析结果
pause
方法二:腾讯内存泄漏检测工具tMemMonitor (TMM)
C/C++由于灵活、高效的优点一直以来都是主流的程序设计语言之一,但是其内存的分配与释放均由程序员自己管理,当由于疏忽或错误造成程序未能释放不再使用的内存时就会造成内存泄漏。在大型、复杂的应用程序中,内存泄漏往往是最常见的问题,因而及时解决内存泄漏非常必要。tMemMonitor (TMM)作为一个专业、准确、易用的内存泄漏分析工具,可以帮助C/C++程序员迅速地解决内存泄漏这个令人头疼的问题。
TMM下载地址(中文版): http://download.csdn.net/detail/tmemmonitor/9444634
TMM下载地址(英文版): http://download.csdn.net/detail/tmemmonitor/9444660
一.背景
目前市面上已有一些Windows平台下的内存泄漏动态检测工具,比如UMDH,VLD,Purify,BoundsCheck等,其中Purify和BoundsCheck是昂贵的商用软件,UMDH需要人工获取内存快照,操作门槛较高,VLD则需要修改源程序的代码,同时这几款工具都存在误报情况,因此准确性不高。针对Windows平台,C/C++程序员迫切需要一款专业、准确、易用的内存泄漏分析工具。
二.TMM简介
TMM是一款运行时C/C++内存泄漏检测工具。TMM认为在进程退出时,堆内存中没有被释放且没有指针指向的无主内存块即为内存泄漏,并进而引入垃圾回收(GC, Garbage Collection)机制,在进程退出时检测出堆内存中所有没有被引用的内存单元,因而内存泄露检测准确率为100%。
TMM工具主要包含两部分,第一部分是客户端的检测界面,客户端部分主要负责监控目标进程中的内存行为并计算内存泄漏。检测时只要将被检测程序添加到监控列表中,然后正常运行被检程序即可,以下为客户端界面:
另一部分是结果的展示与分析。TMM支持本地查看和在线查询两种方式。本地查看时,提供按泄漏次数或泄漏大小对结果进行排序的功能,并在安装目录的data文件夹中给出详细分析报告。用户也可凭QQ帐号登录WeTest网站对内存泄漏情况进行在线查询。以下为本地查看结果:
三.TMM的特性和优点
专业
Ø 最快的注入技术
Ø 基于GC的精准算法,无需内存快照
Ø 无损目标程序性能
Ø 检测结果精准定位到代码堆栈信息
准确
Ø 二次遍历堆内存对象里的指针
Ø 寄存器级的问题跟踪,完整扫描每个线程里32位寄存器内的指针
Ø 不放过全局数据区里的内容
易用
Ø 支持自定义程序
Ø 无须编译,立即使用
Ø 一键操作,无需切换
四.算法原理
1. 替换/注入堆内存分配函数的算法
Windows中有多种级别的内存分配函数,其中,最底层的是ntdll.dll提供的Rtl系列函数,在这之上的有Windows API提供的heap管理函数,再上层,C/C++库提供了malloc/free函数和new/delete操作符,因此替换如此层级复杂的函数比较困难,同时如果替换现有的堆分配函数,则无法做到和原有函数的执行效果完全一致,所以替换原生的堆分配函数对于Windows系统来说几乎是不可行的,那么只能wrap(包裹)这些堆分配函数。
Linux下的内存检测工具,比如Valgrind就采用了包裹堆分配函数的方法,但它包裹的方法是不透明的,在调用栈中会多出额外一帧。TMM则采用全透明的包裹函数,将挂钩函数分为前后二部分(如图1所示)。在函数调用前执行per-hook函数,在函数返回前调用执行post-hook函数。有了前后挂钩函数,TMM就可以在堆分配/释放函数执行前获得参数,并修改分配大小之类的参数;在函数执行后,记录分配的大小和地址、调用栈之类的信息。
图1
2. 泄露检测的算法
TMM使用基于堆内存可访问性的内存泄露检测法( reachability-based leak detection),该算法的核心就是检测(扫描)没有任何指针指向的堆内存,具体分为五步:
Step 1. 进程退出时,suspend所有线程,防止数据在扫描过程中更改。假设此时进程中的堆内存布局如图2所示;
图2
Step 2. 统计root-set,它由每个线程的寄存器、所有非堆内存、所有线程栈帧顶部RSP/ESP以上区域、所有库的数据区组成;
Step 3. 从root-set出发遍历图2,标记出有指针指向的内存块,即beginning reachable blocks,如图3中A、C;
图3
Step 4. 由于beginning reachable blocks也会包含有指针,因此通过遍历beginning reachable blocks可以找出其内部指针指向的内存块并标记,如图4中B;
图4
Step 5. 统计检测出来的内存泄漏,即图4中unreachable的堆内存块D、E、F。
五.使用步骤
图5
1. 在拥有Administrator权限的情况下启动TMM。
2. 在监控列表中右键添加目标程序,正常操作。
3. 正常退出目标程序。
4. 耐心等待检测结果生成(目标程序状态由running变为null时,说明程序正常退出,检测结果生成完毕)。
5. 查看结果。
六.注意事项
1. 安装TMM时,用户应具有Administrator权限,并且TMM不支持中文安装路径。
2. 使用TMM时需要修改注册表,如遇安全软件弹窗警告,可将TMM加入信任列表放心使用。
3. 被检测程序不能是加壳版本,因为加壳程序的函数名和函数地址已经混淆。
4. 被检测程序需是release版本。
5. 如需在分析报告中显示泄漏点详细堆栈信息,请在被检测程序同级目录放置同版本的PDB文件,PDB解析时目录不支持中文。
6. 使用TMM导致被测程序退出时变慢属于正常情况,此时TMM正在统计内存泄漏情况,请不要手动强制结束进程。
七.总结
TMM适用于PC端所有C/C++程序的内存泄漏分析。对于被测程序,不需要修改源代码,运行一次被测程序就能够准确定位泄漏的文件名和行号。TMM是一款专业、准确、易用的内存泄漏检测工具,值得每个程序员拥有。
TMM下载地址(中文版): http://download.csdn.net/detail/tmemmonitor/9444634
TMM下载地址(英文版): http://download.csdn.net/detail/tmemmonitor/9444660
句柄增长
句柄泄露调试(Handles Leak Debug)
一、概述
造成句柄泄露的主要原因,是进程在调用系统文件之后,没有释放已经打开的文件句柄。
对于句柄泄露,轻则影响某个功能模块正常运行,重则导致整个应用程序崩溃。在 Windows系统中, GDI 句柄上限是 12000 个,USER 句柄上限是 18000 个。
与 Windows 系统的设置不同,Linux 系统对进程可以调用的文件句柄数做了限制,在默认情况下,每个进程可以调用的最大句柄数为 1024 个。超过了这个数值,进程则无法获得新的句柄。因此,句柄的泄露将会对进程的功能失效造成极大的隐患。
理论上,我们编程时,1 个进程使用的句柄数建议不应该超过 1000。
二、分析
根据我们项目的测试经验,通常统计出来的句柄图形如下列 3 种:
1、平稳型
图 2-1. 平稳图
在程序运行当中,句柄被不断地打开关闭,因此统计图形呈现平稳的锯齿形。在程序运行后期,很多临时打开的句柄被逐渐关闭,总的句柄数量没有随着时间的推移而增加,因此该程序不存在句柄泄露。
2、峰值稳定型
图 2-2. 峰值稳定图
在该程序运行初期,程序打开的句柄数量会随着时间的推移而逐步增加。但是当运行一
段时间后,句柄数量会达到一个相对平稳的状态,大概 3500 左右。这个时候表明程序打开
了很多临时句柄,但是句柄数量相对稳定,也不存在句柄泄露问题。
3、递增型
图 2-3. 递增图
程序在运行当中,某一操作引起了程序打开句柄数量逐步增加,而且没有出现相对平稳的迹象,说明该程序可能存在句柄泄露,需要进一步分析是哪一部分的句柄存在泄漏,以及什么操作会引起程序句柄的泄露。
通过对程序句柄数量进行采样统计,并且绘制出相应的统计图形,能够以比较直观的方式判断在程序中是否存在句柄泄露。该方法基于程序要运行大量的测试用例,增加测试用例的覆盖率,尽可能多的用测试用例触发程序打开和关闭句柄的操作,这样才能发现潜在的句柄泄露 bug。对于如何能够快速的发现句柄泄露代码,我们将做进一步研究。
方法一:WinDbg工具分析
step1:观察句柄增长规律(资源管理器、性能计数器)
在程序运行约一个小时以后,通过任务管理器发现句柄数超过5000,线程数也超过1000。对于一段只需要并行接收和分析数据的简易代码来说,这显然太不正常了,我们可以判断程序已经产生了泄露。
通过任务管理器可以非常方便的查看程序实时的资源占用情况,但无法了解到历史数据和趋势。程序是一开始就需要分配和使用这么多资源,还是长时间运行的结果?如果是后者,那么是运行过程中平稳持续的增长,还是在某个时间节点之后的突然增长?弄清楚这些问题是必要的,我们可以借此初步判断出内存泄露是与用户的特定操作相关,或者与特定时间点上产生的事件相关;是跟程序的初始化有关,还是跟某些从始至终运行的后台任务相关。
性能监视器可以很直观的显示这一趋势,其中内置了很多有用的计数器,我们可以从图形化界面中观察这些计数器值的变化规律,了解系统和进程的运行状况。使用Win + R组合键打开“运行”窗口,输入perfmon打开性能监视器。点击绿色加号按钮打开“添加计数器”对话框,选择Process中的Handle Count和Thread Count,然后选择LeakExample进程作为实例,添加这两个计数器。
接下来观察这些数值的变化。在这期间,我们像往常一样的使用程序,可以重复进行一些可能造成内存泄露的操作。在运行过一段时间后,得到了如下的图表。句柄数和线程数在持续的增长,很容易猜测到跟Timer有关,因为Timer定期触发,并且每次触发都需要使用线程。即便如此,仍然需要确切的定位究竟是什么对象产生了泄露,因为实际的项目中可能用到的Timer或者后台线程的代码远远不止一两处。
step2:分析运行中的进程
首先应该找出5000多个句柄究竟代表什么对象。利用Process Explorer查看该进程,在下方面板中检查句柄列表,发现有大量的Event句柄和Thread句柄,更进一步的,我们想知道到底有多少Event和Thread。
在这个列表中难以看出各种句柄的数量。可以按下Ctrl+A组合键,将Process Explorer中的进程列表和选中进程的句柄列表保存为文本文件,而后利用你所习惯使用的文本查看工具统计其中特定句柄的数量,我们这里使用Chrome浏览器的搜索功能看到约有4063个Event句柄和1008个Thread句柄。
到这里,我们有一个大致的印象,即泄露的对象是Event和Thread,其中Event占大多数。下一步需要找出是谁创建出了这些对象,可以使用Windbg跟踪对象的创建。Windbg是非常方便的Windows调试工具,可以利用强大的SOS扩展命令诊断.NET程序中的各种问题,最新的Windbg(截止2016年4月)可以从MSDN的Download the WDK, WinDbg, and associated tools页面下载,点击页面上的Get Debugging Tools for Windows (WinDbg)链接即可。
将Windbg附加到LeakExample.exe进程,而后使用!handle和!htrace命令对进程句柄进行分析。!handle命令可以列出进程内所有句柄,也可以查看特定句柄的信息,而!htrace显示句柄的堆栈跟踪。我们先使用!htrace -enable启用句柄跟踪,然后让进程继续运行几分钟时间,再中断程序的执行,用!htrace -diff查看自上次快照以来新打开的句柄。由于命令输出过长,一些不重要的信息被隐去用省略号代替。
step3:定位问题
0、先配置好相应的pdb符号文件
1、运行程序
WinDbg 提供了图形界面和命令行两种运行方式。这里介绍使用图形界面的 WinDbg 来
调试应用程序:
File->OpenExecutable->可以选择一个可执行文件进行调试。
File->Attache to a Process->可以选择一个运行中的进程,并对其进行调试。
图 3-1
2、启动句柄操作的栈回溯
按下 F5 快捷键,第 1 次中断进程运行,用!htrace -enable 命令开启句柄检测;htrace 提
供了进行句柄相关检测的命令,可查看 WinDBG 帮助文档。
图 3-2
同时用 g 命令让程序运行。
图 3-3
3、抓取快照
第 2 次中断进程运行,使用!htrace -snapshot 命令,获得此时进程句柄的镜像。并再次让程序运行。
图 3-4
4、运行程序,定位句柄泄露
第 3 次中断进程运行,使用!htrace -diff 命令获得当前句柄状态与第 3 步 snapshot 镜像句柄的差异;
图 3-5
通过上面的栈回溯信息,很清楚可以看到句柄打开的地方。使用 lsa 命令可以定位到产
生句柄泄露的方法体代码行,lsa handlew2!fun4+0x0000002e :
利用windbg分析崩溃,句柄泄漏,死锁,CPU高,内存泄漏
一、崩溃
1、 输入.ecxr;kbn得到崩溃的堆栈
其中源代码如下
2、 查看堆栈和源代码,发现第0帧导致崩溃,代码也是本地代码
输入.frame 0,切到第0帧如下
3、 输入 dv 查看当前帧的一些变量信息
发现变量p =0x00000000
二、句柄泄漏
1、 启动进程
2、 用windbg附加到进程
3、 !htrace -enable命令开启句柄检测
4、 !htrace –snapshot
5、 运行一段时间后
6、 !htrace –diff
得到如下信息
标红函数创建了event
7、输入lsahandleLeak!ThreadProc1+0x00000037
这样就可以看出代码中在不停CreateEvent。
补充:
可以在windbg调式中,输入!handle可以得到当前堆栈的一些句柄信息,可以看出这个堆栈event非常多。
三、死锁
1、 启动进程
2、 Windbg附加进程
3、 输入~*kv, 输出所有线程
4、 输入!findstackntdll!RtlEnterCriticalSection,查找哪些线程在等待锁
或者看代码某一函数没执行,对比windbg中的线程,找到线程id分析
图1是源代码,图2是执行结果, ThreadProc1函数中的” ThreadProc1 lock g_mutex2”没发生,怀疑是否死锁了
5、windbg中线程信息如下,发现ThreadProc1在等某一把锁
第三帧是本地代码对比源代码发现在等锁g_mutex2;
第二帧是系统函数在等待锁,锁地址为00bf7140
6、输入!cs 00bf7140,查看这把锁信息
发现锁的占有者是0x00002cc4
7、输入~~[0x00002cc4],发现对应是3号线程
8、切到3号线程,并输出堆栈
发现代码27号,也在等一把锁,锁地址00bf7158
9、同理输出锁信息
10、发现锁占有者0x00004c80,切到线程0x00004c80,并输出堆栈
发现是刚才的2号线程
至此分析完成2号线程和3号线程发生死锁。
四、CPU高
1、 启动进程
2、 Windbg附加进程
3、 输入!runaway
4、 发现2号线程cpu最高,切到2号线程,并输出堆栈
5、 可以得到是cpuhigh!ThreadProc1+0x35
五、内存泄漏
5.1、windbg手动分析
1、设置gflags.exe,这工具和windbg在同一目录
2、 windbg附加到进程,输入!heap –s
3、程序运行一段时间之后,再次输入!heap–s
发现00970000这个堆有增加,其他无变化
4、输入!heap -stat -h00970000,查看这个堆状态
发现这个堆的内存主要是被大小为0x224的块占用
5、输入!heap -flt s 224, 查看224这些块被谁在使用
6、输入!heap -p -a 00971d20,查看堆栈
7、 已经得到堆栈,本地有源代码,还可以查看代码,
输入lsa memoryleak!ThreadProc1+0x00000048
5.2、利用umdh分析
1、同5.1设置gflags配置
2、开启命令窗口cmd,输入要定位内存泄露的程序gflags.exe /i memoryleak.exe +ust
3、 设置程序的符号表路径
SET _NT_SYMBOL_PATH=SRV*C:\Symbols*http://msdl.microsoft.com/download/symbols;F:\windbgtest\Debug
4、 启动memoryleak.exe,利用umdh创建第一次heap快照
输入umdh-pn:memoryleak.exe -f:memory1.log
5、 运行一段时间后,再次输入快照,umdh -pn:memoryleak.exe -f:memory2.log
6、 分析前后2次快照的差异umdh -dmemory1.log memory2.log -f:memoryleak.log
会在当前路径下面生成memoryleak.log,打开分析
7、
定位到代码,需要具体分析逻辑,查看是否真的泄漏,还是还没来得及释放。
from:https://blog.csdn.net/zqw_4181/article/details/79162309