代码改变世界

记一次Windows Embedded CE应用程序崩溃的摸索

2010-05-17 18:55  王克伟  阅读(...)  评论(... 编辑 收藏

一次很难忘的Bug Fix,记录一下以便以后归纳总结一下。对于应用程序、系统关键进程Crash等Bug相信会比较让人头痛,这次就让我头疼了1个星期,
一方面因为代码规模较大,另一方面对这样的Bug没有什么经验。

Bug Description

打开一个Word文档,“另存为”操作时弹出“没有足够的内存保存文档”,并抛出异常:

 297006 PID:a6b00fe TID:a6c00fe Exception 'Access Violation' (14): Thread-Id=0a6c00fe(pth=8d88c000),
        Proc-Id=0a6b00fe(pprc=868f38e4) 'pword.exe', VM-active=0a6b00fe(pprc=868f38e4) 'pword.exe'
 297006 PID:a6b00fe TID:a6c00fe PC=4006dea9(coredll.dll+0x0004dea9) RA=40042ebc(coredll.dll+0x00022ebc) SP=0005d918, BVA=00449000

 

解决过程

初步定位到问题代码是:
传递到private\winceos\coreos\core\locale\utf.c L256函数中的cchWideChar参数为0xffffffff(-1)

int UnicodeToUTF(
    UINT CodePage,
    DWORD dwFlags,
    LPCWSTR lpWideCharStr,
    int cchWideChar,
    LPSTR lpMultiByteStr,
    int cbMultiByte,
    LPCSTR lpDefaultChar,
    LPBOOL lpUsedDefaultChar)

此时的Callstack为:

COREDLL!UnicodeToUTF(unsigned int 0x0000fde9, unsigned long 0x00000000, const wchar_t * 0x00449820, int 0xffffffff, char * 0x00000000, int 0x00000000, const char * 0x00000000, int * 0x00000000) utf.c line 288 + 3 bytes
COREDLL!WideCharToMultiByte(unsigned int 0x0000fde9, unsigned long 0x00000000, const wchar_t * 0x00449820, int 0xffffffff, char * 0x00000000, int 0x00000000, const char * 0x00000000, int * 0x00000000) codepage.cpp line 2609 + 27 bytes
OSSVCS!UnicodeToMB(const wchar_t * 0x00449820, unsigned int 0x0000fde9, char * * 0x0005da0c, unsigned long * 0x0005da10) helpers.cpp line 1483
OSSVCS!DecodeBase64W(const wchar_t * 0x00449820, unsigned char * * 0x0005da94, unsigned long * 0x0005da98, tagBASE64_OPTIONS BASE64_OPTION_IGNOREINVALIDCHARACTER) base64.cpp line 463 + 19 bytes
PWWIFF!OnEndFldData(PDSItem * 0x00000000, PDSItem * 0x00449720, RDPItemData * 0x0005daac) cdwhelpers.cpp line 11259 + 23 bytes
PWWIFF!CRDParser::EndElement() rdparser.cpp line 375 + 8 bytes
PWORD!SendSAXEndElement(ISAXContentHandler * 0x003e02e4, const wchar_t * const 0x0005db28, unsigned long 0x0000000a) saxutilcmn.cpp line 93 + 33 bytes
PWORD!CRosettaReader::SendEndElement(const wchar_t * const 0x0005db28, unsigned long 0x0000000a) crosettareader.cpp line 70 + 14 bytes
PWORD!CRosettaReader::DoTreetoXML(void * 0x003dba20) crosettareader.cpp line 392
PWORD!CRosettaReader::DoTreetoXML(void * 0x003db8a0) crosettareader.cpp line 368 + 13 bytes
PWORD!CRosettaReader::DoTreetoXML(void * 0x003db860) crosettareader.cpp line 368 + 13 bytes
PWORD!CRosettaReader::DoTreetoXML(void * 0x003db820) crosettareader.cpp line 368 + 13 bytes
PWORD!CRosettaReader::parse(tagVARIANT {...}) crosettareader.cpp line 233
PWORD!CAFWrite::SendSubTreeToDF(void * 0x003db820) convwrite.cpp line 699 + 30 bytes
PWORD!CAFWrite::UpdateState() convwrite.cpp line 600 + 10 bytes
PWORD!CAFWrite::EndBodyChildCore(unsigned int 0x00000000, long 0x003db820) convwrite.cpp line 559 + 7 bytes
PWORD!AFWriteWndProc(HWND__ * 0x70055220, unsigned int 0x00000468, unsigned int 0x00000000, long 0x003db820) convwrite.cpp line 760
GWES!WindowProcCallback(void * 0x0a6b00fe, long (HWND__ *, unsigned int, unsigned int, long)* 0x000381ab, CWindow * 0x70055220, unsigned int 0x00000468, unsigned int 0x00000000, long 0x003db820, bool * 0xd7f6f18f) wbase.cpp line 3198 + 18 bytes
GWES!CWindow::CallWindowProcW_I(CePtr_t<long (__cdecl*)(HWND__ *,unsigned int,unsigned int,long)> {...}, HWND__ * 0x000381ab, unsigned int 0x00000468, unsigned int 0x00000000, long 0x003db820, SendMsgEntry_t * 0x00000000) wbase.cpp line 3403 + 21 bytes
GWES!MsgQueue::SendMessageWithOptions(HWND__ * 0x70055220, unsigned int 0x00000468, unsigned int 0x00000000, long 0x003db820, unsigned int 0x00000000) msgque.cpp line 4110 + 29 bytes
GWES!MsgQueue::SendMessageW_I(HWND__ * 0x70055220, unsigned int 0x00000468, unsigned int 0x00000000, long 0x003db820) msgque.cpp line 5437 + 22 bytes
GWES!MsgQueue::SendMessageW_E(HWND__ * 0x70055220, unsigned int 0x00000468, unsigned int 0x00000000, long 0x003db820) msgque.cpp line 5372 + 17 bytes
GWES!PixelDoubled_t::SendMessageW_I(HWND__ * 0x70055220, unsigned int 0x00000468, unsigned int 0x00000000, long 0x003db820) pixeldouble.cpp line 1928 + 18 bytes
PWORD!CRosettaContentHandler::DispatchAppFilterMessage(CTreeElement<DConvNode> * 0x003d8520, unsigned int 0x00000468) rosettacontenthandler.cpp line 318 + 5 bytes
PWORD!CRosettaContentHandler::endElement(const wchar_t * 0x43a93adc, int 0x00000000, const wchar_t * 0x43a9ab34, int 0x00000002, const wchar_t * 0x43a9ab30, int 0x00000004) rosettacontenthandler.cpp line 884
PWWIFF!SendSAXEndElement(ISAXContentHandler * 0x0015bda0, const wchar_t * const 0x43a9ab30, unsigned long 0x00000004) saxutilcmn.cpp line 93 + 33 bytes
PWWIFF!CDocReader::SendEndElement(const wchar_t * const 0x43a9ab30, unsigned long 0x00000004) cdocreader.cpp line 804 + 14 bytes
PWWIFF!CDocReader::EndWaitableWrapper(CDocReader::XMLRunState XRS_Paragraph) cdocreader.cpp line 8815 + 13 bytes
PWWIFF!CDocReader::RenderParagraph(PlxInfo * 0x003e1e28, unsigned char * 0x003e2b50, unsigned long 0x00000006, unsigned short 0x0000, int 0x00000000, long 0x00000000, PropStatusObj * 0x0005ec38) cdocreader.cpp line 5468 + 24 bytes
PWWIFF!CDocReader::RenderCurrentPapxFkp(PlxInfo * 0x003e1e28, int 0x00000000, long 0x00000000, PropStatusObj * 0x0005ec38) cdocreader.cpp line 5907 + 28 bytes
PWWIFF!CDocReader::GetNextPap(PlxInfo * 0x003e1e28, PropStatusObj * 0x0005ec38, long * 0x00000b8a, long * 0x0005ea54, int * 0x0005ea38) cdocreader.cpp line 6334 + 15 bytes
PWWIFF!CDocReader::XMLEmitMainBody() cdocreader.cpp line 9036 + 42 bytes
PWWIFF!CDocReader::DoXMLConversion() cdocreader.cpp line 2659 + 7 bytes
PWORD!BuildDConvTree(const wchar_t * 0x0005f3d4, ISequentialStream * 0x00000000, ISAXXMLReader * 0x003e13e0) dconv.cpp line 408 + 13 bytes
PWORD!DConvHandleFile(const wchar_t * 0x0005f3d4, HWND__ * 0x70055220, void * * 0x0005f24c, DCONV_FILEOP DCONV_WRITE, _GUID {...}) dconv.cpp line 636 + 10 bytes
PWORD!DConvCreateFile(const wchar_t * 0x0005f3d4, PWRD_FILE_TYPE PWRD_CONVERTFILE_TYPE_DOC, HWND__ * 0x70055220, void * * 0x0005f24c, DCONV_FILEOP DCONV_WRITE) dconv.cpp line 681 + 23 bytes
PWORD!AFWrite(const wchar_t * 0x0005f3d4, PWRD_FILE_TYPE PWRD_CONVERTFILE_TYPE_DOC, IDocFilter * 0x003e02e0, CRosettaReader * 0x003d02a0, ISAXContentHandler * 0x003e02e4, long 0x00000000, unsigned long 0x000003f7, PWRD_XML_VERSION PW_XML_VER_O11) convwrite.cpp line 94 + 18 bytes
PWORD!DConvWriteFile(const wchar_t * 0x0005f3d4, PWRD_FILE_TYPE PWRD_CONVERTFILE_TYPE_DOC, PWRD_XML_VERSION PW_XML_VER_O11, const wchar_t * 0x0006dbae, PWRD_FILE_TYPE PWRD_CONVERTFILE_TYPE_DOC, long 0x00000000, unsigned long 0x000003f7) dconv.cpp line 846 + 29 bytes
PWORD!SaveDOCorXMLFile(tagINKW * 0x00010002, const wchar_t * 0x0005f3d4, const wchar_t * 0x0006dbae) appfilter.cpp line 405 + 29 bytes
PWORD!PureREDisplayToDocFile(const wchar_t * 0x0006dbae) appfilter.cpp line 310 + 10 bytes
PWORD!REDisplayToDocFile(tagINKW * 0x0006d960, const wchar_t * 0x0005f3d4, const wchar_t * 0x0006dbae, PWRD_FILE_TYPE PWRD_CONVERTFILE_TYPE_DOC) appfilter.cpp line 230 + 18 bytes
PWORD!CreateOrOpenFile(tagINKW * 0x0006d960, int * 0x00000000, int 0x00000000, int * 0x0005f3c0, wchar_t * 0x0005f3d4) doc2.cpp line 2382 + 17 bytes
PWORD!SaveDoc(tagINKW * 0x0006d960, SD_SAVEAS SAVEDOC_SAVEAS, int * 0x00000000, SD_LOWMEM SAVEDOC_NOT_LOWMEM, SD_WAITCURSOR SAVEDOC_WAITCURSOR, SD_AUTOSAVE SAVEDOC_NOT_AUTOSAVE, SD_PROMPT SAVEDOC_PROMPT_DONTPROMPT) doc2.cpp line 3126 + 32 bytes
PWORD!HandleCommand(tagINKW * 0x0006d960, unsigned int 0x000063a1, long 0x70053d40) commands.cpp line 979 + 15 bytes
PWORD!InkWWndProc(HWND__ * 0x70052340, unsigned int 0x00000111, unsigned int 0x000063a1, long 0x70053d40) msghdlr.cpp line 2234 + 12 bytes
GWES!WindowProcCallback(void * 0x0a6b00fe, long (HWND__ *, unsigned int, unsigned int, long)* 0x0001ea4f, CWindow * 0x70052340, unsigned int 0x00000111, unsigned int 0x000063a1, long 0x70053d40, bool * 0xd7f6f407) wbase.cpp line 3198 + 18 bytes
GWES!CWindow::CallWindowProcW_I(CePtr_t<long (__cdecl*)(HWND__ *,unsigned int,unsigned int,long)> {...}, HWND__ * 0x0001ea4f, unsigned int 0x00000111, unsigned int 0x000063a1, long 0x70053d40, SendMsgEntry_t * 0x00000000) wbase.cpp line 3403 + 21 bytes
GWES!MsgQueue::SendMessageWithOptions(HWND__ * 0x70052340, unsigned int 0x00000111, unsigned int 0x000063a1, long 0x70053d40, unsigned int 0x00000000) msgque.cpp line 4110 + 29 bytes
GWES!MsgQueue::SendMessageW_I(HWND__ * 0x70052340, unsigned int 0x00000111, unsigned int 0x000063a1, long 0x70053d40) msgque.cpp line 5437 + 22 bytes
GWES!MsgQueue::SendMessageW_E(HWND__ * 0x70052340, unsigned int 0x00000111, unsigned int 0x000063a1, long 0x70053d40) msgque.cpp line 5372 + 17 bytes
GWES!PixelDoubled_t::SendMessageW_I(HWND__ * 0x70052340, unsigned int 0x00000111, unsigned int 0x000063a1, long 0x70053d40) pixeldouble.cpp line 1928 + 18 bytes
PWORD!WorkerHandleCommand(HWND__ * 0x700539a0, tagINKW * 0x0006d960, unsigned int 0x000063a1, long 0x70053d40) workwin.cpp line 334 + 18 bytes
PWORD!WorkerWndProc(HWND__ * 0x700539a0, unsigned int 0x00000111, unsigned int 0x000063a1, long 0x70053d40) workwin.cpp line 721
GWES!WindowProcCallback(void * 0x0a6b00fe, long (HWND__ *, unsigned int, unsigned int, long)* 0x0001d205, CWindow * 0x700539a0, unsigned int 0x00000111, unsigned int 0x000063a1, long 0x70053d40, bool * 0xd7f6f67f) wbase.cpp line 3198 + 18 bytes
GWES!CWindow::CallWindowProcW_I(CePtr_t<long (__cdecl*)(HWND__ *,unsigned int,unsigned int,long)> {...}, HWND__ * 0x0001d205, unsigned int 0x00000111, unsigned int 0x000063a1, long 0x70053d40, SendMsgEntry_t * 0x00000000) wbase.cpp line 3403 + 21 bytes
GWES!MsgQueue::SendMessageWithOptions(HWND__ * 0x700539a0, unsigned int 0x00000111, unsigned int 0x000063a1, long 0x70053d40, unsigned int 0x00000000) msgque.cpp line 4110 + 29 bytes
GWES!MsgQueue::SendMessageW_I(HWND__ * 0x700539a0, unsigned int 0x00000111, unsigned int 0x000063a1, long 0x70053d40) msgque.cpp line 5437 + 22 bytes
GWES!MsgQueue::SendMessageW_E(HWND__ * 0x700539a0, unsigned int 0x00000111, unsigned int 0x000063a1, long 0x70053d40) msgque.cpp line 5372 + 17 bytes
GWES!PixelDoubled_t::SendMessageW_I(HWND__ * 0x700539a0, unsigned int 0x00000111, unsigned int 0x000063a1, long 0x70053d40) pixeldouble.cpp line 1928 + 18 bytes
AYGSHELL!MenuWndProc(HWND__ * 0x70053c20, unsigned int 0x00000111, unsigned int 0x000063a1, long 0x70053d40) menus.cpp line 1701 + 15 bytes
GWES!WindowProcCallback(void * 0x0a6b00fe, long (HWND__ *, unsigned int, unsigned int, long)* 0x40da4e3d, CWindow * 0x70053c20, unsigned int 0x00000111, unsigned int 0x000063a1, long 0x70053d40, bool * 0xd7f6f8f7) wbase.cpp line 3198 + 18 bytes
GWES!CWindow::CallWindowProcW_I(CePtr_t<long (__cdecl*)(HWND__ *,unsigned int,unsigned int,long)> {...}, HWND__ * 0x40da4e3d, unsigned int 0x00000111, unsigned int 0x000063a1, long 0x70053d40, SendMsgEntry_t * 0x00000000) wbase.cpp line 3403 + 21 bytes
GWES!MsgQueue::SendMessageWithOptions(HWND__ * 0x70053c20, unsigned int 0x00000111, unsigned int 0x000063a1, long 0x70053d40, unsigned int 0x00000000) msgque.cpp line 4110 + 29 bytes
GWES!MsgQueue::SendMessageW_I(HWND__ * 0x70053c20, unsigned int 0x00000111, unsigned int 0x000063a1, long 0x70053d40) msgque.cpp line 5437 + 22 bytes
GWES!MsgQueue::SendMessageW_E(HWND__ * 0x70053c20, unsigned int 0x00000111, unsigned int 0x000063a1, long 0x70053d40) msgque.cpp line 5372 + 17 bytes
GWES!PixelDoubled_t::SendMessageW_I(HWND__ * 0x70053c20, unsigned int 0x00000111, unsigned int 0x000063a1, long 0x70053d40) pixeldouble.cpp line 1928 + 18 bytes
AYGSHELL!BubbleBarProc(HWND__ * 0x70053d40, unsigned int 0x00000111, unsigned int 0x000063a1, long 0x70053d40) menus_p.cpp line 1678 + 13 bytes
GWES!WindowProcCallback(void * 0x0a6b00fe, long (HWND__ *, unsigned int, unsigned int, long)* 0x40da1be2, CWindow * 0x70053d40, unsigned int 0x00000111, unsigned int 0x000063a1, long 0x70053d40, bool * 0xd7f6fb6f) wbase.cpp line 3198 + 18 bytes
GWES!CWindow::CallWindowProcW_I(CePtr_t<long (__cdecl*)(HWND__ *,unsigned int,unsigned int,long)> {...}, HWND__ * 0x40da1be2, unsigned int 0x00000111, unsigned int 0x000063a1, long 0x70053d40, SendMsgEntry_t * 0x00000000) wbase.cpp line 3403 + 21 bytes
GWES!MsgQueue::DispatchMessageW_I(const tagMSG * 0x0005facc) msgque.cpp line 4967 + 32 bytes
GWES!PixelDoubled_t::DispatchMessageW_I(const tagMSG * 0x0005facc) pixeldouble.cpp line 2083 + 9 bytes
COREDLL!DispatchMessageW(const tagMSG * 0x0005facc) twinuser.cpp line 2947 + 9 bytes
PWORD!WinMain(HINSTANCE__ * 0x0a6b00fe, HINSTANCE__ * 0x00000000, wchar_t * 0x0005fc20, int 0x00000005) inkword.cpp line 850 + 9 bytes
PWORD!WinMainCRTStartupHelper(HINSTANCE__ * 0x0a6b00fe, HINSTANCE__ * 0x00000000, unsigned short * 0x0005fc20, int 0x00000005) pegwmain.c line 71 + 17 bytes
PWORD!WinMainCRTStartup(HINSTANCE__ * 0x0a6b00fe, HINSTANCE__ * 0x00000000, unsigned short * 0x0005fc20, int 0x00000005) pegwmain.c line 104 + 17 bytes
COREDLL!MainThreadBaseFunc(void * 0x0001becd, const wchar_t * 0x0005fc0

分析private\apps\woffice\word\main\doc2.cpp L2902的SaveDoc函数:
private\winceos\coreos\core\locale\utf.c L308的字符串指针lpWideCharStr没有结束符是造成Access Violation异常:

    if (cchWideChar <= -1)
    {
        cchWideChar = wcslen(lpWideCharStr) + 1;
    }

对应的X86汇编代码(我对汇编不熟,这些只是看一下关键的部分,我们调试Release版本的Code时因为编译器优化的原因经常发现Visual Studio“闹鬼”了,
这时可以借助x86和ARM汇编,也可以直接用RETAILMSG打出Log):

306:      if (cchWideChar <= -1)
4006DE9E    cmp         edx,0FFFFFFFFh
4006DEA1    jg          UnicodeToUTF+77h (4006debb)
307:      {
308:          cchWideChar = wcslen(lpWideCharStr) + 1;
4006DEA3    mov         ecx,edi
4006DEA5    push        esi
4006DEA6    lea         esi,[ecx+2]
4006DEA9    mov         dx,word ptr [ecx]
4006DEAC    inc         ecx
4006DEAD    inc         ecx
4006DEAE    cmp         dx,ax
4006DEB1    jne         UnicodeToUTF+65h (4006dea9)
4006DEB3    sub         ecx,esi
4006DEB5    sar         ecx,1
4006DEB7    lea         edx,[ecx+1]
4006DEBA    pop         esi
309:      }

那么谁传递的没有结束符的字符串,搞的大爷崩溃了?

突破口:
1.private\apps\woffice\word\filters\rosetta\crosettareader.cpp L351:

    if(!(wcscmp(k_szCharacters, szName)))
    {
        //then we have to read the child and emit the characters
        //We do not emit the start and end element tags here
        CHRA(DoCharacters(hRoot));
    }
    else
    {
        CHRA(SendStartElementFromHandle(hRoot));

        CHRA(DConvGetChildHandle(hRoot, nIndex, &hChild));
        //DConvGetChildHandle returns NULL when there are no more children.
        
        while(hChild)
        {
            // Process the children depth first
            // NOTE: this is a recursive call
            CHRA(DoTreetoXML(hChild));

            //Get the next child here
            //But before you do that, free the current child handle, 
            //to avoid memory leaks
            VERIFY(SUCCEEDED(DConvCloseHandle(&hChild)));

            //If for whatever reason the close handle did not succeed, 
            //we will go over the previous tag again.
            //So watch out if the Verify on the previous call fires an assert.  
            //We may then need to error check the closehandle call and 
            //increment nChild in that case.
            CHRA(DConvGetChildHandle(hRoot, ++nIndex, &hChild));
        }

        //At this point the heirarchy of this element is done, 
        //so send the end element tag
        CHRA(SendEndElement(szName, cchName));
    }

2.private\apps\woffice\word\filters\pwwiff\cdwhelpers.cpp L2318:

    ulmemcpy(prt->pwcText + prt->cchText, wzText, cchText * sizeof(*wzText));

3.private\winceos\coreos\core\locale\utf.c L306:

    if (cchWideChar <= -1)
    {
        cchWideChar = wcslen(lpWideCharStr) + 1;
    }

设置这3个断点,然后分析流程到底怎样,为什么lpWideCharStr指向的数据是:

A.N.D.J.6.n.n.5.u.s
.4.R.j.I.I.A.q.g.B.
L.q.Q.s.C.A.A.A.A.F
.w.A.A.A.B.Y.A.A.A.
B.o.A.H.Q.A.d.A.B.w
.A.D.o.A.L.w.A.v.A.
G.g.A.e.Q.B.w.A.G.U
.A.c.g.B.s.A.G.k.A.

时就出现问题,这些数据到底代表什么?

摸索中…

经验证这是经常出现的数据,可能是标志位,标志图片或者链接之类的,它是0x4C个Unicode字符,占0x98字节:

004AE560  41 00 4E 00 44 00 4A 00 36 00 6E 00 6E 00 35 00 75 00 73  A.N.D.J.6.n.n.5.u.s
004AE573  00 34 00 52 00 6A 00 49 00 49 00 41 00 71 00 67 00 42 00  .4.R.j.I.I.A.q.g.B.
004AE586  4C 00 71 00 51 00 73 00 43 00 41 00 41 00 41 00 41 00 46  L.q.Q.s.C.A.A.A.A.F
004AE599  00 77 00 41 00 41 00 41 00 42 00 59 00 41 00 41 00 41 00  .w.A.A.A.B.Y.A.A.A.
004AE5AC  42 00 6F 00 41 00 48 00 51 00 41 00 64 00 41 00 42 00 77  B.o.A.H.Q.A.d.A.B.w
004AE5BF  00 41 00 44 00 6F 00 41 00 4C 00 77 00 41 00 76 00 41 00  .A.D.o.A.L.w.A.v.A.
004AE5D2  47 00 67 00 41 00 65 00 51 00 42 00 77 00 41 00 47 00 55  G.g.A.e.Q.B.w.A.G.U
004AE5E5  00 41 00 63 00 67 00 42 00 73 00 41 00 47 00 6B 00 41 00  .A.c.g.B.s.A.G.k.A.
004AE5F8  00 00

出现问题时的内存区,可以看到字符串结束符没有了:

0498020  41 00 4E 00 44 00 4A 00 36 00 6E 00 6E 00 35 00 75 00 73  A.N.D.J.6.n.n.5.u.s
00498033  00 34 00 52 00 6A 00 49 00 49 00 41 00 71 00 67 00 42 00  .4.R.j.I.I.A.q.g.B.
00498046  4C 00 71 00 51 00 73 00 43 00 41 00 41 00 41 00 41 00 46  L.q.Q.s.C.A.A.A.A.F
00498059  00 77 00 41 00 41 00 41 00 42 00 59 00 41 00 41 00 41 00  .w.A.A.A.B.Y.A.A.A.
0049806C  42 00 6F 00 41 00 48 00 51 00 41 00 64 00 41 00 42 00 77  B.o.A.H.Q.A.d.A.B.w
0049807F  00 41 00 44 00 6F 00 41 00 4C 00 77 00 41 00 76 00 41 00  .A.D.o.A.L.w.A.v.A.
00498092  47 00 67 00 41 00 65 00 51 00 42 00 77 00 41 00 47 00 55  G.g.A.e.Q.B.w.A.G.U
004980A5  00 41 00 63 00 67 00 42 00 73 00 41 00 47 00 6B 00 41 00  .A.c.g.B.s.A.G.k.A.
004980B8  62 00 67 00 42 00 72 00 41 00 44 00 45 00 41 00 4C 00 67  b.g.B.r.A.D.E.A.L.g
004980CB  00 42 00 6A 00 41 00 47 00 38 00 41 00 62 00 51 00 41 00  .B.j.A.G.8.A.b.Q.A.
004980DE  41 00 41 00 4F 00 44 00 4A 00 36 00 6E 00 6E 00 35 00 75  A.A.O.D.J.6.n.n.5.u
004980F1  00 73 00 34 00 52 00 6A 00 49 00 49 00 41 00 71 00 67 00  .s.4.R.j.I.I.A.q.g.
00498104  42 00 4C 00 71 00 51 00 73 00 75 00 41 00 41 00 41 00 41  B.L.q.Q.s.u.A.A.A.A
00498117  00 61 00 41 00 42 00 30 00 41 00 48 00 51 00 41 00 63 00  .a.A.B.0.A.H.Q.A.c.
0049812A  41 00 41 00 36 00 41 00 43 00 38 00 41 00 4C 00 77 00 42  A.A.6.A.C.8.A.L.w.B
0049813D  00 6F 00 41 00 48 00 6B 00 41 00 63 00 41 00 42 00 6C 00  .o.A.H.k.A.c.A.B.l.
00498150  41 00 48 00 49 00 41 00 62 00 41 00 42 00 70 00 41 00 47  A.H.I.A.b.A.B.p.A.G
00498163  00 34 00 41 00 61 00 77 00 41 00 78 00 41 00 43 00 34 00  .4.A.a.w.A.x.A.C.4.
00498176  41 00 59 00 77 00 42 00 76 00 41 00 47 00 30 00 41 00 4C  A.Y.w.B.v.A.G.0.A.L
00498189  00 77 00 41 00 41 00 41 00 41 00 3D 00 3D 00 A5 A6 A7 A8  .w.A.A.A.A.=.=.....

下一步采用不同文档进一步排除和缩小范围,使用更简单的文档来分析:
即使保存空的文档也还是有Exception抛出,但是没有内存不足的提示,这个异常跟内存不足错误是否有关系?

 635630 PID:2700002 TID:85e0002 Exception 'Access Violation' (14): Thread-Id=085e0002(pth=85b52ad0), Proc-Id=02700002(pprc=8f91c7f4) 'udevice.exe', VM-active=02700002(pprc=8f91c7f4) 'udevice.exe'
 635644 PID:2700002 TID:85e0002 PC=4002f680(coredll.dll+0x0000f680) RA=40031450(coredll.dll+0x00011450) SP=001bf7a8, BVA=011c1e44
 635662 PID:7790092 TID:b9a00ca Exception 'Access Violation' (14): Thread-Id=0b9a00ca(pth=850f7980), Proc-Id=07790092(pprc=8ba28de4) 'pword.exe', VM-active=07790092(pprc=8ba28de4) 'pword.exe'
 635712 PID:7790092 TID:b9a00ca PC=4002f680(coredll.dll+0x0000f680) RA=4002f6bf(coredll.dll+0x0000f6bf) SP=0005eb78, BVA=011c1e44

看来Word代码本身就有问题。

A.Word文档里面连续存在4个(有时3个)带链接的和回车的文字比如:

http://hyperlink2.com
mailto:hyperlink3.com
http://hyperlink1.com
http://hyperlink1.com

或者B.内容较多时只要有3个以上带链接的的文字
“另存为”时就会出现内存不够保存文档,并且NK.exe报错。

首先分析A情况:

PWWIFF!AddTextToRdpText(RdpText * 0x004497c0, const wchar_t * const 0x0043a8a0, const unsigned long 0x0000004c, CInternalHeap * 0xcccccccc) cdwhelpers.cpp line 2319
PWWIFF!OnCharactersT(const wchar_t * const 0x0043a8a0, const unsigned long 0x0000004c, PDSItem * 0x004494e0, RDPItemData * 0x0005d864) cdwhelpers.cpp line 5796 + 22 bytes
PWWIFF!CRDParser::Characters(const wchar_t * 0x0043a8a0, unsigned long 0x0000004c) rdparser.cpp line 476 + 62 bytes
PWWIFF!CDocWriter::CDWContentHandler::characters(const wchar_t * 0x0043a8a0, int 0x0000004c) cdocwriter.cpp line 6819
PWORD!SendSAXCharacters(ISAXContentHandler * 0x003e02e4, const wchar_t * 0x0043a8a0, unsigned long 0x0000004c) saxutilcmn.cpp line 113 + 12 bytes
PWORD!CRosettaReader::SendCharacters(const wchar_t * 0x0043a8a0, unsigned long 0x0000004c) crosettareader.cpp line 77 + 14 bytes
PWORD!CRosettaReader::DoCharacters(void * 0x0043a860) crosettareader.cpp line 314 + 13 bytes
PWORD!CRosettaReader::DoTreetoXML(void * 0x0043a860) crosettareader.cpp line 357
PWORD!CRosettaReader::DoTreetoXML(void * 0x0043a820) crosettareader.cpp line 368 + 13 bytes
PWORD!CRosettaReader::DoTreetoXML(void * 0x0043a720) crosettareader.cpp line 368 + 13 bytes
PWORD!CRosettaReader::DoTreetoXML(void * 0x0043a6e0) crosettareader.cpp line 368 + 13 bytes
PWORD!CRosettaReader::DoTreetoXML(void * 0x0043a6a0) crosettareader.cpp line 368 + 13 bytes
PWORD!CRosettaReader::parse(tagVARIANT {...}) crosettareader.cpp line 233
PWORD!CAFWrite::SendSubTreeToDF(void * 0x0043a6a0) convwrite.cpp line 699 + 30 bytes
PWORD!CAFWrite::UpdateState() convwrite.cpp line 600 + 10 bytes
PWORD!CAFWrite::EndBodyChildCore(unsigned int 0x00000000, long 0x0043a6a0) convwrite.cpp line 559 + 7 bytes
PWORD!AFWriteWndProc(HWND__ * 0x70055380, unsigned int 0x00000468, unsigned int 0x00000000, long 0x0043a6a0) convwrite.cpp line 760

问题点很可能在AddTextToRdpText,在以下代码执行之前:

    ulmemcpy(prt->pwcText + prt->cchText, wzText, cchText * sizeof(*wzText));
    prt->cchText += cchText;

wzText的值为0x0043a8a0 "bgBrADMALgBjAG8AbQAAAODJ6nn5us4RjIIAqgBLqQssAAAAbQBhAGkAbAB0AG8AOgBoAHkAcABl"
prt->pwcText的值为0x00449800 "ANDJ6nn5us4RjIIAqgBLqQsCAAAAFwAAABYAAABtAGEAaQBsAHQAbwA6AGgAeQBwAGUAcgBsAGkA
而执行之后
prt->pwcText的值变为0x00449800 "ANDJ6nn5us4RjIIAqgBLqQsCAAAAFwAAABYAAABtAGEAaQBsAHQAbwA6AGgAeQBwAGUAcgBsAGkAbgBrADMALgBjAG8AbQAAAODJ6nn5us4RjIIAqgBLqQssAAAAbQBhAGkAbAB0AG8AOgBoAHkAcABl 再一次prt->pwcText的值变为0x00449800 "ANDJ6nn5us4RjIIAqgBLqQsCAAAAFwAAABYAAABtAGEAaQBsAHQAbwA6AGgAeQBwAGUAcgBsAGkAbgBrADMALgBjAG8AbQAAAODJ6nn5us4RjIIAqgBLqQssAAAAbQBhAGkAbAB0AG8AOgBoAHkAcABlAHIAbABpAG4AawAzAC4AYwBvAG0AAAA= 然后进入private\winceos\coreos\core\locale\utf.c中的UnicodeToUTF函数L306:

    if (cchWideChar <= -1)
    {
        cchWideChar = wcslen(lpWideCharStr) + 1;//此处因为字符串没有结束符出现问题
    }

此时lpWideCharStr的值跟prt->pwcText一样,且都没有结束符。
进一步分析:
private\apps\woffice\word\filters\pwwiff\cdwhelpers.cpp第11255行传进去的字符串指针prtElt->pwcText指向的字符串没有结束符,
而DecodeBase64W函数(在private\ossvcs\services\misc\base64.cpp中)假设字符串是有结束符的:

    // Decode the input
    CHRA(DecodeBase64W(prtElt->pwcText, 
                       (LPBYTE *)&szDecoded, 
                       &cchDecoded, 
                       BASE64_OPTION_IGNOREINVALIDCHARACTER)); 

在这之前加如下代码:

    *((prtElt->pwcText)+(prtElt->cchText)) = L'\0';

可以正常运行的更久一点,但是稍后抛出了一个堆异常。
用CeDebugX工具查看一下,发现应该是分配堆得时候分配小了一点,直接这样修改内存:

    *((prtElt->pwcText)+(prtElt->cchText)) = L'\0';

破坏了堆的标记所以事后抛出了堆错误异常:

+++++++++++++ PROBLEMS DETECTED +++++++++++++ 
 id Confidence           Severity                 Description
===========================================================================================
  1 Certain              Application fatal        Heap corruption 'Bad item tail signature' detected in pword.exe
  2 Certain              Application fatal        Heap corruption 'Bad item tail signature' detected in pword.exe
  3 Certain              Application fatal        Heap corruption 'Bad item tail signature' detected in pword.exe
  4 Certain              Application fatal        Heap corruption 'Bad item tail signature' detected in pword.exe
  5 Certain              Application fatal        Unknown Exception caused by pword.exe
  6 Certain              Application fatal        Deadlock in NK.EXE
  7 Certain              Application fatal        Deadlock in NK.EXE
  8 Certain              Application fatal        Deadlock in NK.EXE
  9 Possible             System fatal             CPU Starvation may be generated by servicesd.exe thread (CTEpDispatchThread)

Windows CE>!diagnose 1
=======================================================
| 
|  Heap corruption 'Bad item tail signature' detected in pword.exe
| 
=======================================================

Heap allocations are aligned to a heap block size of 16 bytes.
When heap sentinels are enabled the difference between the requested
allocation size and the 16 byte block boundary is filled with a tail
signature.

The tail signature of the following item appears to be corrupt.  The
most likely cause is a heap item over-run, i.e. an app has written to
memory beyond the requested heap item size.

addr           value
========================== 
header                     
========================== 
0x00470980  :  0xa9e4b608   Signature (expect 0xa9e4b620)
0x00470984  :  0x00000178   Data size (bytes)
0x00470988  :  0x43a9d83b   pwwiff.dll ! CInternalHeap::InternalReAlloc + 0x7f
0x0047098c  :  0x43a9d887   pwwiff.dll ! _InternalHeapReAlloc + 0x16
0x00470990  :  0x43ab23b9   pwwiff.dll ! AddTextToRdpText + 0x30
0x00470994  :  0x43ab2b61   pwwiff.dll ! OnCharactersT + 0x19
0x00470998  :  0x43abef93   pwwiff.dll ! CRDParser::Characters + 0x69
0x0047099c  :  0x43aaeb26   pwwiff.dll ! CDocWriter::CDWContentHandler::characters + 0x14

Tags for bug matcher:
+DEFECT:CRASH:HEAP_CORRUPTION:pword.exe:|Type=Bad item tail signature;

将private\apps\woffice\word\filters\pwwiff\cdwhelpers.cpp中的L2308从:

        // We already ahve something.  We need to realloc.
        szText = (LPWSTR) InternalHeapReAlloc(
                                       prt->pwcText, 
                                       LMEM_MOVEABLE,
                                       (prt->cchText+cchText)*sizeof(*wzText));

修改为(多留16字节保持Unicode字符串结束符):

        // We already ahve something.  We need to realloc.
        szText = (LPWSTR) InternalHeapReAlloc(
                                       prt->pwcText, 
                                       LMEM_MOVEABLE,
                                       (prt->cchText+cchText+1)*sizeof(*wzText));

问题解决了。

 

简单总结

1.Word在处理字符串时不是采用结束符标记的,而是直接记录字符串的长度,但是调用System的API时要记得添加上这些结束符。

2.解决问题需要很强的对知识综合应用的能力,Bug Fixing占项目中很长的时间,而且直接影响着软件质量。