07年10月11日补充:注意,该方法只能定位显性泄漏,定位到的C语句一定产生泄漏了,但可能这个位置是"理论上"不会出问题的代码.那么这是由于同进程内其他代码泄漏而影响了进程的堆区或栈区(隐性泄漏,这个地方不会产生data
abort exception),然后被定位出来的代码才被动地显性泄露.产生data abort.
在篇末我给出代码实例来说明这种情况.
-----------------------
首先在DEBUG版本中定位DATA
ABORT的方法,地球人应该都知道了吧,我就不废话了。PlatformBuilder或VS2005、EVC这类IDE工具会在DEBUG模式下自动停在出错的那句,情况就很显然了。
RELEASE版本下的泄漏就要稍微麻烦一点,如何快速定位呢?
案例一:用EVC编译的应用程序泄漏
首先我做了一个内存泄漏的程序walzer_leak.exe,里面做了一个泄漏的函数
void DoLeak()
{
}
编译的时候注意先在project settings里的Link页里勾选“Generate
mapfile”,会生成一个map文件。如下图
把编译出来的RELEASE版本可执行文件放到CE5平台下运行。出错的时候串口打印了一句
Data Abort: Thread=83ad1d38 Proc=820266d0 'walzer_leak.exe'
AKY=00000021 PC=00011008(walzer_leak.exe+0x00001008)
RA=00011030(walzer_leak.exe+0x00001030) BVA=81000000
FSR=0000000d
这句是系统自动输出的。我们得到了一个关键的信息:PC指针。和PC指针在walzer_leak.exe中的偏移量。然后打开编译时生成的walzer_leak.map文件
,文件不长,我贴一下
************************************************************
Walzer_leak
************************************************************
OK,首先,里面有一句“Preferred load address is 00010000”,这意味着DATA ABORT那句的PC=00011008(walzer_leak.exe+0x00001008) 我们必须把括
号里的0x1008加上这个load address的偏移量,得到0x11008(注意不能直接用PC,一会儿再给个案例就知道了),然后我们在函数偏移列表里看Rva+Base
这栏,找到0x11008落在了DoLeak函数的地址范围里,所以是DoLeak函数泄漏了。
案例二:在OAL层做了一个泄漏的函数,用Platform
Builder进行RELEASE版编译并LINK到NK.exe里,然后应用程序Call_Leak.exe调用该函数导致泄漏
步骤类似,只是Platform Builder默认就会在RELEASE目录下生成.map文件。应用程序去调用泄漏的OAL函数时,出现
Data Abort: Thread=822fc000 Proc=820267c0 'Call_Leak.exe'
AKY=00000041 PC=8023e3c8(NK.EXE+0x0003e3c8)
RA=8023e1d4(NK.EXE+0x0003e1d4)
BVA=8e000000 FSR=00000005
注意我们这次是NK.EXE里制造泄漏,所以PC指针不是在0x00011008这样的Slot
0低地址了,而是在0x80000000以上的KERNEL区域了。
nk.map很长,我选择关键段落来贴
************************************************************
************************************************************
所以 NK.EXE + 0x0003e3c8 = 0x10000(Preferred load address) + 0x0003e3c8 = 0x4e3c8, 落在了OALIoCtlHal_DoLeak函数里
结论:原理上很简单,就是利用DATA
ABORT消息中的PC值,配合MAP文件可以快速定位到泄漏的函数。定位到之后,嘿嘿,谁LEAK谁请客咯。
-------------------------------
10月11日补充:
用上文的方法,不但网友kevin没有成功定位到泄漏的原因(见后面回帖,他定位到微软USBFN的MDD层代码),我这项目组里的人也没抓到原因(定位到PRIVATE下LoadLibrary函数相关的代码)。昨晚我想了下,这个方法的确有漏洞,早上我做了个试验,建立个DialogBox,主处理函数如下
static DWORD* g_pTest = NULL;
int CALLBACK LeakProc(HWND hDlg, UINT uMsg,
WPARAM wParam, LPARAM lParam)
{
}
我运行了几次,每次该进程的BaseAddr = 0x1A000000, 而
(1)
在ID_LEAK按钮按下后,模拟一次内存泄漏,直接对&g_pTest地址上的数值改写,把malloc后的0x00030230改成0x00000000,
注意虽然该处已经泄漏了,但是并没有产生data abort exception.
(2)
然后在ID_CLOSE按钮按下后,装做不知道前面那处泄漏,代码风格严谨地先判断下指针是否为空,然后试图在malloc得到的堆区0x00030230地址上写个值,但是由于g_pTest这个指针已经被改写为指向其他进程空间了,所以在*g_pTest
= 1这句产生data abort exception并停下来了。
(3) 所以,按照上文的方法,只能抓到*g_pTest =
1这句泄露,而实际上这句是无辜的,真正的元凶*((int*)0x1A013860) =
0x1C000000这句却没有被抓出来。
结论:这篇文章给出的方法不好用啊。