Chinaunix首页 | 论坛 | 博客
  • 博客访问: 222258
  • 博文数量: 53
  • 博客积分: 1410
  • 博客等级: 上尉
  • 技术积分: 507
  • 用 户 组: 普通用户
  • 注册时间: 2008-07-22 13:38
文章分类

全部博文(53)

文章存档

2009年(1)

2008年(52)

我的朋友

分类:

2008-08-29 14:24:50

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

#1楼    回复  引用    

2007-09-28 21:12 by
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? 困惑我很久..........,谢谢,

#2楼 [楼主]   回复  引用  查看    

2007-09-28 23:03 by Walzer      
楼上客气了,既然是相互学习,就不用以溢美之辞开篇嘛。这篇文章给出的方法是今天早上我和同事刚研究出来的,除了两个试验外,还缺乏能够真正解决实际问题的经验。我期望用这个方法可以直接定位到某个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来看待,也许这篇文章对你会有所帮助。

#3楼    回复  引用    

2007-10-09 13:45 by
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

#4楼 [楼主]   回复  引用  查看    

2007-10-10 22:10 by Walzer      
@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管辖下的其他驱动代码里就不能排除嫌疑.

#5楼    回复  引用    

2007-11-30 09:00 by
请教下
我用evc4.2 sp4,sdk是smdk2440_916,运行平台是wince4.0的板上,
为什么用debug版本很多异常都无法定位到出错位置,堆栈都是数字没有函数名,如:divide by zero异常。你提到的DATA ABORT异常也有些是定位不到位置。而且那些异常用try{}catch(...)也无法捕获到,很难定位到出问题的地方,调试很不方便,你遇到这样的情况吗??能给我提点建议吗?

#6楼 [楼主]   回复  引用  查看    

2007-11-30 12:54 by Walzer      
@sailing
是的,我也经常遇到你说的情况, DEBUG版本遇到异常时, IDE里显示一堆汇编. 什么都看不到. 这种情况下只能靠在各函数的开始和结束加debug message来慢慢跟踪出问题的地方了.
我的经验是在除法语句前面用assert判断一下除数是否为零, 这样就可以在出现divide by zero之前, 停在assert那句;还有在自己不太确定的指针访问之前,对指针assert一下,尤其是几个线程中都用到的指针。写代码的时候多写几句,后面调试时就方便了。

#7楼    回复  引用    

2007-12-01 15:28 by
先多谢提的建议,确实编码的时候要多注意这些,而且有些是要功力和经验慢慢积累。现在项目里人参差不齐,这个问题不能马上解决。ce上找问题真的让我头大,现在我有时是让程序在pc上跑,有些问题在pc上能定位到,对于那些在ce上才会暴露出来的问题,那种还是办法,就只能在ce环境里跟踪,交替玩来提高点效率。我原本一直做pc的软件,现在在wince好多工具用不上。查看程序hdc,内存使用情况有用什么工具能用吗?测试的话有没有什么好工具介绍。

#8楼    回复  引用    

2008-01-25 16:36 by
....这样定位,耗时又耗神.还不一定准,用工具定位吧...我猜你应该知道用什么工具.可以直接定位到source code 哦!

#9楼 [楼主]   回复  引用  查看    

2008-01-28 09:47 by Walzer      
@mowenli
我还真不知道. 楼上赐教一下,你用什么工具定位?

#10楼    回复  引用    

2008-03-10 11:42 by
data abort是数据访问异常,arm cpu的7大异常之最常见版,对data abort楼主的方法相当好用.不过这和内存泄漏是两回事,标准意义上的内存泄漏是在堆上分配了内存却没有指针指向此内存,造成此内存无法再回收使用.查找内存泄漏通常实践中都是先用pclint工具走查一遍,去掉一些低级错误引起的,如果没有发现,那么用隔离二分法来判断,在目标化码中不断插入取系统内存状态代码比较,有较大差异时再去研究如果没有还没有发现,include,重写malloc和free函数,自已记录所有分配状况,再查,如果还没有发现,考虑多线程因素,一个线程一个线程查,严查线程间传递的内存区域.如果还没有发现,移植到PC端,用PC端工具查,boundchecker等.

#11楼 [楼主]   回复  引用  查看    

2008-03-13 09:57 by Walzer      
@bsxy
多谢bsxy分享一些好方法.
目前我还只会用重写malloc/free函数,加上次数记录的方法来查而已

#12楼    回复  引用  查看    

2008-04-03 09:40 by wangjs      
请问下面的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中,如何查找这个出错位置?

#13楼    回复  引用    

2008-04-03 14:55 by
你好!我在用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
该如何解决?谢谢,希望你能给点提示,即使定位到了还需要怎么修改?

#14楼    回复  引用    

2008-06-23 14:19 by
请问下面的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

#15楼 [楼主]   回复  引用  查看    

2008-06-23 16:05 by Walzer      
上文在最末一行已经说了,这篇文章说的方法是不好用、不正确的了。只不过作为一种思路尝试仍然留在BLOG里面。
楼上各位,出了DATA ABORT就得看个人造化慢慢去查了, 尤其是device.exe和gwes.exe出了data abort, 80%可能就是BSP写得有问题导致.

阅读(4372) | 评论(1) | 转发(0) |
给主人留下些什么吧!~~

chinaunix网友2009-06-30 18:44:38

#10楼 说的没错,本文应是抓"Data Abort"的方法,或可称作"程序异常",和Leak泄漏是两码事。不过所述调试情形和方法倒是准确。 可惜,为何文末尾楼主自己得出“这篇文章说的方法是不好用、不正确的”之结论? 关于楼主的DialogBox\LeakProc\g_pTest 实验, *((int*)0x1A013860) = 0x1C000000; //这只是修改指针,未访问非法地址,自然不会有Data Abort,并非本文方法的错 if(g_pTest) {   *g_pTest = 1;//这里访问地址0x1C000000,非法地址 所以,不存在真正的元凶漏网的问题,而是,既然抓到了*g_pTest = 1这里,就可以在使用之前检查一下g_pTest所指,然后查所有可能修改g_pTest指向的代码就是。若不是自己代码控制范围,至少可以在锁定的出错位置检查memory是否写入合法,不合法则跳过,这样程序就不会abort。 关于DATA ABORT在nk.exe和gwes.exe等位置的,可以通过编译平台时生成的map文件按楼主所述方法定位出错函数如