07年10月11日补充:注意,该方法只能定位显性泄漏,定位到的C语句一定产生泄漏了,但可能这个位置是"理论上"不会出问题的代码.那么这是由于同进程内其他代码泄漏而影响了进程的堆区或栈区(隐性泄漏,这个地方不会产生data abort exception),然后被定位出来的代码才被动地显性泄露.产生data abort. 在篇末我给出代码实例来说明这种情况.
-----------------------
首先在DEBUG版本中定位DATA ABORT的方法,地球人应该都知道了吧,我就不废话了。PlatformBuilder或VS2005、EVC这类IDE工具会在DEBUG模式下自动停在出错的那句,情况就很显然了。
RELEASE版本下的泄漏就要稍微麻烦一点,如何快速定位呢?
案例一:用EVC编译的应用程序泄漏
首先我做了一个内存泄漏的程序walzer_leak.exe,里面做了一个泄漏的函数
void DoLeak()
{
int* p = (int*)0x81000000;
*p = 10;
}
编译的时候注意先在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
Timestamp is 46fcbb17 (Fri Sep 28 16:28:07 2007)
Preferred load address is 00010000
Start Length Name Class
0001:00000000 00000258H .text CODE
0002:00000000 00000014H .xdata DATA
0002:00000014 00000014H .idata$2 DATA
0002:00000028 00000014H .idata$3 DATA
0002:0000003c 00000010H .idata$4 DATA
0002:0000004c 0000000cH .idata$6 DATA
0002:00000058 00000000H .edata DATA
0003:00000000 00000010H .idata$5 DATA
0003:00000010 00000004H .CRT$XCA DATA
0003:00000014 00000004H .CRT$XCZ DATA
0003:00000018 00000004H .CRT$XIA DATA
0003:0000001c 00000004H .CRT$XIZ DATA
0003:00000020 00000004H .CRT$XPA DATA
0003:00000024 00000004H .CRT$XPZ DATA
0003:00000028 00000004H .CRT$XTA DATA
0003:0000002c 00000004H .CRT$XTZ DATA
0003:00000030 00000009H .bss DATA
0004:00000000 00000038H .pdata DATA
0005:00000000 00000010H .rsrc$01 DATA
0005:00000010 00000000H .rsrc$02 DATA
Address Publics by Value Rva+Base Lib:Object
0001:00000000 ?DoLeak@@YAXXZ 00011000 f Walzer_leak.obj
0001:00000010 WinMain 00011010 f Walzer_leak.obj
0001:0000002c WinMainCRTStartup 0001102c f corelibc:pegwmain.obj
0001:000000a0 _cinit 000110a0 f corelibc:crt0dat.obj
0001:00000210 exit 00011210 f corelibc:crt0dat.obj
0001:00000228 _XcptFilter 00011228 f coredll:COREDLL.dll
0001:00000238 __C_specific_handler 00011238 f coredll:COREDLL.dll
0001:00000248 LocalFree 00011248 f coredll:COREDLL.dll
0002:00000014 __IMPORT_DESCRIPTOR_COREDLL 00012014 coredll:COREDLL.dll
0002:00000028 __NULL_IMPORT_DESCRIPTOR 00012028 coredll:COREDLL.dll
0003:00000000 __imp___C_specific_handler 00013000 coredll:COREDLL.dll
0003:00000004 __imp_LocalFree 00013004 coredll:COREDLL.dll
0003:00000008 __imp__XcptFilter 00013008 coredll:COREDLL.dll
0003:0000000c \177COREDLL_NULL_THUNK_DATA 0001300c coredll:COREDLL.dll
0003:00000010 __xc_a 00013010 corelibc:crt0init.obj
0003:00000014 __xc_z 00013014 corelibc:crt0init.obj
0003:00000018 __xi_a 00013018 corelibc:crt0init.obj
0003:0000001c __xi_z 0001301c corelibc:crt0init.obj
0003:00000020 __xp_a 00013020 corelibc:crt0init.obj
0003:00000024 __xp_z 00013024 corelibc:crt0init.obj
0003:00000028 __xt_a 00013028 corelibc:crt0init.obj
0003:0000002c __xt_z 0001302c corelibc:crt0init.obj
0003:00000030 __onexitend 00013030
0003:00000034 __onexitbegin 00013034
0003:00000038 _exitflag 00013038
entry point at 0001:0000002c
Static symbols
0001:0000010c doexit 0001110c f corelibc:crt0dat.obj
************************************************************
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很长,我选择关键段落来贴
************************************************************
kern
Timestamp is 46fca696 (Fri Sep 28 15:00:38 2007)
Preferred load address is 00010000
...
0001:0003d2c8 OALIoCtlHal_GetDeviceId 0004e2c8 f oal_ioctl:deviceid.obj
0001:0003d398 OALIoCtlHal_DoLeak 0004e398 f oal_ioctl:leaktest.obj
0001:0003d438 OALIoCtlHal_DdkCall 0004e438 f oal_io:ioctl.obj
...
************************************************************
所以 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)
{
switch(uMsg)
{
case WM_INITDIALOG:
g_pTest = (DWORD*)malloc(1);
break;
case WM_COMMAND:
switch(wParam)
{
case ID_LEAK:
*((int*)0x1A013860) = 0x1C000000;
break;
case ID_CLOSE:
if(g_pTest)
{
*g_pTest = 1;
}
EndDialog(hDlg, 0);
break;
}
break;
}
return FALSE;
}
我运行了几次,每次该进程的BaseAddr = 0x1A000000, 而 &g_pTest = 0x1A013860,在进程的全局变量区;malloc之后g_pTest = 0x00030230在进程的堆区。所以我
(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这句却没有被抓出来。
结论:这篇文章给出的方法不好用啊。
Feedback
dear walzer:
看你的BLOG有一段时间了,没想到现在找一个做CE开发的人,那么难,从你的笔下,可以看出,你真的是个"牛人",很想和你一起学习CE.
呵呵~,废话少说,我现在在做CE6的活,目前碰到了一个难点,请教您一下:
我的USB DRIVER在启动时出了点问题,打出了下面的MESSAGE:
Exception 'Data Abort' (4): Thread-Id=00d40002(pth=8fa5d6ec), Proc-Id=00400002(pprc=8c719308) 'NK.EXE', VM-active=01540002(pprc=8fa2ec58) 'udevice.exe'
PC=c09aafd0(usbfn.dll+0x0000afd0) RA=c09ac040(usbfn.dll+0x0000c040) SP=d03de250, BVA=00000000
按照你的方法,我试了一下,发现,问题在MDD层的ufnbus.cpp中,在AllocEndpoints()函数中 new CStaticPipe()时,没有成功,查看了一下.MAP 文件,发现上述的PC指的位置在:
0001:00009f30 ?PopulateCache@?$CFreePool@$03V?$fixed_block_allocator@$07@ce@@Utransfer_funcs@@@@IAAXXZ 1000af30 f i UFNMDDBASE:pipe.obj
0001:00009fd8 ??0?$list@PAVCUfnMddTransfer@@V?$fixed_block_allocator@$0BE@@ce@@@ce@@QAA@ABV?$fixed_block_allocator@$0BE@@1@@Z 1000afd8 f i UFNMDDBASE:pipe.obj
请问您碰到过吗?有没有什么IDEA? 困惑我很久..........,谢谢,
楼上客气了,既然是相互学习,就不用以溢美之辞开篇嘛。这篇文章给出的方法是今天早上我和同事刚研究出来的,除了两个试验外,还缺乏能够真正解决实际问题的经验。我期望用这个方法可以直接定位到某个OEM完成的接口函数里,但是当我试图用这种方法来解决自己项目的一处泄漏时,PC指针居然定位到PRIVATE里面的KERNEL下的代码,真是无语。
我看了下,按照这篇文章的思路,你泄漏的位置应该是在common\oak\drivers\usbfn\controller\mdd\pipe.c所包含的头文件Xferlist.h里的CFreePool.PopulateCache函数里。PIPE处理完全在MDD层里面,和OEM所做的硬件寄存器操作无关,所以在这里泄漏也是匪夷所思。
如果你非要一点建议的话,我觉得
(1) DATA ABORT可以分两类,“强ABORT”(每次运行必定出现)和“弱ABORT”(概率性出现),如果你觉得自己的ABORT足够“强”,那么最好的方法就是编译一个带KITL的DEBUG版本,让它运行到PopulateCache里面挂掉,然后PB就会停在那句泄漏的地方了。“弱ABORT”的定位方法我仍然在摸索中,我们可以一起探讨研究
(2) Mike Hall有一篇How to Use Remote Tools to Track Memory Leaks in Windows CE Applications的文章,URL是 由于CE5的device.exe也是运行在USER SPACE里面,也可以当作一个applications来看待,也许这篇文章对你会有所帮助。
Walzer,
最近在忙CE5的驱动向CE6 PORTING,前些时候看见你也在搞CE6,现在想问你一下,
The USB driver (usbfn.dll) exports the “Data abort” in the step of system initializing. After tracing I find the problem appeared in the AllocEndpoints() function in the ufnbus.cpp (MDD). The “Data abort” comes from at the line of
“CStaticPipe *pStaticPipe = new CStaticPipe(dwPipe, &pContext->PddInfo, pContext->pFreeTransferList, pContext);”, which may be caused of cannot allocate the memory for the CStaticPipe.
请问你一下,对于这样无法分配给类空间,你有没有什么好的解决办法.?
谢谢!-----------kevin
@kevin_83
我现在看来文章这种定位方法存在漏洞.
比如你device.exe管理所有驱动的dll, 在USB以外的某个DLL里内存泄露了,但是泄露的地址不远, 是在device.exe进程空间内部, 那么运行时候泄露的那句是不会产生data abort等exception的(我们给这种情况起个名字叫"隐性泄漏"). 这处泄露正好影响到了你usbfn.dll的堆栈空间, 那么最后报data abort的就变成usbfn.dll这块了(显性泄漏), 那么usbfn这块就被白白冤枉了.
所以说, 文章这种方法只能查出显性泄露, 而不能查出隐性泄露, 所以不能100%成功定位泄漏所在的代码行.
我设想了两个方案来查你这个问题
(1) 在定位出来的这句前面加DBGMSG输出,把pContext, pFreeTransferList, &pContext->PddInfo这些指针地址打印出来, 看看是否是这句显性泄漏了, 也验证一下文章提的定位方法.
(2) 在注册表里尽量去掉device.exe加载的外设驱动, 尽量缩小成一个"最小化系统" + usbfn.dll, 看看这样是否还会出问题. 如果这样不出问题了,那么device.exe管辖下的其他驱动代码里就不能排除嫌疑.
请教下
我用evc4.2 sp4,sdk是smdk2440_916,运行平台是wince4.0的板上,
为什么用debug版本很多异常都无法定位到出错位置,堆栈都是数字没有函数名,如:divide by zero异常。你提到的DATA ABORT异常也有些是定位不到位置。而且那些异常用try{}catch(...)也无法捕获到,很难定位到出问题的地方,调试很不方便,你遇到这样的情况吗??能给我提点建议吗?
@sailing
是的,我也经常遇到你说的情况, DEBUG版本遇到异常时, IDE里显示一堆汇编. 什么都看不到. 这种情况下只能靠在各函数的开始和结束加debug message来慢慢跟踪出问题的地方了.
我的经验是在除法语句前面用assert判断一下除数是否为零, 这样就可以在出现divide by zero之前, 停在assert那句;还有在自己不太确定的指针访问之前,对指针assert一下,尤其是几个线程中都用到的指针。写代码的时候多写几句,后面调试时就方便了。
先多谢提的建议,确实编码的时候要多注意这些,而且有些是要功力和经验慢慢积累。现在项目里人参差不齐,这个问题不能马上解决。ce上找问题真的让我头大,现在我有时是让程序在pc上跑,有些问题在pc上能定位到,对于那些在ce上才会暴露出来的问题,那种还是办法,就只能在ce环境里跟踪,交替玩来提高点效率。我原本一直做pc的软件,现在在wince好多工具用不上。查看程序hdc,内存使用情况有用什么工具能用吗?测试的话有没有什么好工具介绍。
....这样定位,耗时又耗神.还不一定准,用工具定位吧...我猜你应该知道用什么工具.可以直接定位到source code 哦!
@mowenli
我还真不知道. 楼上赐教一下,你用什么工具定位?
data abort是数据访问异常,arm cpu的7大异常之最常见版,对data abort楼主的方法相当好用.不过这和内存泄漏是两回事,标准意义上的内存泄漏是在堆上分配了内存却没有指针指向此内存,造成此内存无法再回收使用.查找内存泄漏通常实践中都是先用pclint工具走查一遍,去掉一些低级错误引起的,如果没有发现,那么用隔离二分法来判断,在目标化码中不断插入取系统内存状态代码比较,有较大差异时再去研究如果没有还没有发现,include,重写malloc和free函数,自已记录所有分配状况,再查,如果还没有发现,考虑多线程因素,一个线程一个线程查,严查线程间传递的内存区域.如果还没有发现,移植到PC端,用PC端工具查,boundchecker等.
@bsxy
多谢bsxy分享一些好方法.
目前我还只会用重写malloc/free函数,加上次数记录的方法来查而已
请问下面的DataAbort信息:
Data Abort: Thread=8eeab240 Proc=8c4d0470 'PushEmail.exe'
AKY=00002001 PC=03f700bc(coredll.dll+0x000220bc) RA=03f6ffa4(coredll.dll+0x00021fa4) BVA=1c000013 FSR=00000001
偏移地址在coredll.dll中,如何查找这个出错位置?
你好!我在用VS2005在wince5.0做串口通信程序,出现如下问题:
Data Abort: Thread=83c625f8 Proc=82326340 'device.exe'
AKY=00000405 PC=02981b5c(serial_smdk2440.dll+0x00001b5c) RA=0298450c(serial_smdk2440.dll+0x0000450c) BVA=06000000 FSR=00000007
RaiseException: Thread=83c625f8 Proc=82326340 'device.exe'
AKY=00000405 PC=03f8dfe8(coredll.dll+0x0001dfe8) RA=8029d1c0(NK.EXE+0x0009d1c0) BVA=00000001 FSR=00000001
该如何解决?谢谢,希望你能给点提示,即使定位到了还需要怎么修改?
请问下面的DataAbort信息:
Data Abort: Thread=845459e8 Proc=804e77f0 'gwes.exe'
AKY=20000021 PC=00035714(gwes.exe+0x00025714) RA=00035714(gwes.exe+0x00025714) BVA=0c0dd1e9 FSR=00000001
TLSKERN_NOFAULT set... bypassing kernel debugger.
偏移地址在gwes.exe中,如何查找这个出错位置?
msn:jameshana@hotmail.com
上文在最末一行已经说了,这篇文章说的方法是不好用、不正确的了。只不过作为一种思路尝试仍然留在BLOG里面。
楼上各位,出了DATA ABORT就得看个人造化慢慢去查了, 尤其是device.exe和gwes.exe出了data abort, 80%可能就是BSP写得有问题导致.