很多時(shí)候,我們不知道如何重現(xiàn)一個(gè)crash問題,只有一些log或者dump,拿到一個(gè)這樣的crash的問題,并不知道是什么原因,怎么樣來慢慢分析,這篇文章就舉了一個(gè)現(xiàn)實(shí)的例子,看看怎么查找一個(gè)crash問題的原因。
這個(gè)是我們軟件發(fā)生的一次崩潰,只在客戶的環(huán)境上發(fā)生,沒有人知道在實(shí)驗(yàn)室里怎么重現(xiàn),好在客戶那邊給出了windows的dump文件,這樣就可以很容易的查看當(dāng)時(shí)的案發(fā)現(xiàn)場了。
用windbg打開dump文件,打開匯編窗口,點(diǎn)擊調(diào)用堆棧最上面的函數(shù),就可以看到如下匯編代碼
2e252013 51 push ecx 2e252014 53 push ebx 2e252015 57 push edi 2e252016 8bf9 mov edi,ecx 2e252018 33db xor ebx,ebx 2e25201a 395f04 cmp dword ptr [edi+4],ebx 2e25201d 7454 je nqp+0x22073 (2e252073) 2e25201f 66395f14 cmp word ptr [edi+14h],bx 2e252023 763c jbe nqp+0x22061 (2e252061) 2e252025 895dfc mov dword ptr [ebp-4],ebx 2e252028 56 push esi 2e252029 8da42400000000 lea esp,[esp] 2e252030 8b7704 mov esi,dword ptr [edi+4] 2e252033 0375fc add esi,dword ptr [ebp-4] 2e252036 837e0800 cmp dword ptr [esi+8],0 ds:0023:613d634e=???????? <<-----軟件在這里就崩潰了
通過調(diào)用堆棧,我們可以找到對應(yīng)的函數(shù)的源碼
void HTMLelement:: ResetPriv() { if (i_pAttributeList) { for (int i = 0; i < i_totAttrs; i++) { GetAttributeEntry(i)->Reset(); } } }
可以看出來 是在cmp dword ptr [esi+8],0 的時(shí)候出了異常,這個(gè)指令能出的異常,也就是 [esi+8]不能讀,這說明 esi+8這個(gè)地址錯(cuò)了,但是為啥會(huì)錯(cuò)呢,只能是esi的值不對。先看看esi是多少,在命令窗口 輸入 r esi
0:036> r esi Last set context: esi=613d6346
這個(gè)613d6346 顯然是不可訪問的內(nèi)存。所以,我們就需要看看esi是哪里來的。一行行的往上看:
add esi,dword ptr [ebp-4]
這句把ebp-4里面的數(shù)加到esi上,那我們需要看看ebp-4是多少了,在命令窗口里面輸入 dd ebp-4
0:036> dd ebp-4 32e3cc10 00000000 32e3cc24 2e252966 32e47284 32e3cc20 32e47218 32e3cc44 2e252e24 32e47284 32e3cc30 00000000 00000001 32e47218 32e47218 32e3cc40 00000000 32e3e9fc 2e3abbd6 32e3e960 32e3cc50 00000000 00000000 00000001 32e3f8f0 32e3cc60 00000000 2e462e8c 00000000 32e3cc84 32e3cc70 00000001 016edda8 32e3cf5c 03434cf8 32e3cc80 034348f8 32e3cc90 60001780 016edda8
可見 ebp-4是0,這很正常,這個(gè)ebp-4是個(gè)局部變量i,其實(shí)就是個(gè)數(shù)組的下標(biāo),0不像是個(gè)越界的值,那么就需要繼續(xù)往前看
mov esi,dword ptr [edi+4]
esi的值是來自edi+4,那我們來看看edi+4是啥
0:036> dd edi 32e3e960 32257974 613d6346 00000075 00000042 32e3e970 00000002 00000002 00000000 00000000 32e3e980 32e3e9a0 00000000 00000000 00000019 32e3e990 00000002 00000000 00000000 00000000 32e3e9a0 32e3e960 00000000 00000000 0000006e 可見這個(gè)值就是613d6346,就是esi的值,那就說明 這個(gè)內(nèi)存里面的值有問題,出錯(cuò)的可能有兩個(gè),一種可能是這個(gè)內(nèi)存的值不對,另一種可能是是edi的值不對,導(dǎo)致取值取錯(cuò)了地方。跟蹤edi還是容易一點(diǎn),所以我們繼續(xù)往上看edi的值是哪里來的。
2e252016 8bf9 mov edi,ecx
這個(gè)是函數(shù)的開頭,我們知道ecx放的就是對象的地址,也就是this,說明edi里面就是this,這就說明edi出錯(cuò)的可能比較小了,除非上面
函數(shù)傳值的時(shí)候就傳錯(cuò)了,看了一下源碼,這個(gè)HTMLelement的地址應(yīng)該沒啥太大機(jī)會(huì)出錯(cuò)。這里不是說完全排除,但是進(jìn)一步跟蹤的代價(jià)比較大了,所
以我們先放一下,看另外一個(gè)可能。那就是這個(gè)對象里面的內(nèi)容出錯(cuò)了。
越界的問題往往是數(shù)組越界,而字符串是最容易越界的,我們來看看這個(gè)會(huì)是啥字符,輸入 dc edi 看看
:036> dc edi 32e3e960 32257974 613d6346 00000075 00000042 ty%2Fc=au...B... 32e3e970 00000002 00000002 00000000 00000000 ................ 32e3e980 32e3e9a0 00000000 00000000 00000019 ...2............ 32e3e990 00000002 00000000 00000000 00000000 ................ 32e3e9a0 32e3e960 00000000 00000000 0000006e `..2........n... 32e3e9b0 00000002 00000000 00000000 00000000 ................ 32e3e9c0 2e46beb4 00000000 3607274c 00000091 ..F.....L'.6.... 32e3e9d0 32e3e9a0 00000000 00000000 00000033 ...2........3...
很明顯這里應(yīng)該是是個(gè)指針的地方,居然看起來像個(gè)字符串了,往前看看完整的字符串是啥
0:036> dc edi-128 edi+16 32e3e838 00000000 00000000 00000000 00000000 ................ 32e3e848 00000000 00000001 00000001 ffffffff ................ 32e3e858 2e2f2e2e 64242f2e 75616665 6976746c ../../$defaultvi 32e3e868 372f7765 45314243 43443944 43463944 ew/7CB1ED9DCD9FC 32e3e878 38424145 36353235 30334337 32453630 EAB852567C3006E2 32e3e888 2f454244 65704f3f 636f446e 6e656d75 DBE/?OpenDocumen 32e3e898 72502674 74655365 6c656946 683d7364 t&PreSetFields=h 32e3e8a8 7465535f 64616552 6e656353 5f683b65 _SetReadScene;h_ 32e3e8b8 75636553 79746972 626d654d 6e497265 SecurityMemberIn 32e3e8c8 682c6f66 6d654d5f 4e726562 3b656d61 fo,h_MemberName; 32e3e8d8 4a3d6e63 61696c75 3032256e 73726143 cn=Julian%20Cars 32e3e8e8 32256e6f 3d756f46 69737542 7373656e on%2Fou=Business 32e3e8f8 50303225 6e6e616c 25676e69 6e613032 %20Planning%20an 32e3e908 30322564 6f736552 65637275 46322573 d%20Resources%2F 32e3e918 533d756f 65647574 3225746e 646e6130 ou=Student%20and 32e3e928 43303225 756d6d6f 7974696e 53303225 %20Community%20S 32e3e938 69767265 25736563 756f4632 6174533d ervices%2Fou=Sta 32e3e948 32256666 4d3d6f46 73616e6f 30322568 ff%2Fo=Monash%20 32e3e958 76696e55 69737265 32257974 613d6346 University%2Fc=a 32e3e968 00000075 00000042 00000002 00000002 u...B...........
看來前面是個(gè)很長的字符串,數(shù)數(shù)看,從字符串開始的地方 32e3e858 算,有270多個(gè)字符,估計(jì)有某個(gè)地方定義了個(gè)256的緩存區(qū),然后沒做檢查,就把后面的棧上的內(nèi)存給覆蓋了,繼續(xù)看stack,看看這個(gè)字符數(shù)組是屬于哪個(gè)函數(shù)的棧幀。k可以看函數(shù)調(diào)用棧
0:036> k *** Stack trace for last set context - .thread/.cxr resets it ChildEBP RetAddr WARNING: Stack unwind information not available. Following frames may be wrong. 32e3cc14 2e252966 nqp+0x22036 32e3cc24 2e252e24 nqp+0x22966 32e3cc44 2e3abbd6 nqp+0x22e24 32e3e9fc 2e3ae8d7 nqp+0x17bbd6 32e3eb40 2e3ace45 nqp+0x17e8d7 32e3eb84 2e3c9823 nqp+0x17ce45 32e3ebbc 2e2fb73b nqp+0x199823 32e3ebcc 2e301e22 nqp+0xcb73b 32e3ffe4 2e318255 nqp+0xd1e22 32e402a8 2e271b68 nqp+0xe8255 32e42c78 2e27236a nqp+0x41b68 32e42cc0 600bbf1a nqp+0x4236a
因?yàn)槲椰F(xiàn)在沒有當(dāng)時(shí)那個(gè)版本的pdb,所以顯示不了具體的函數(shù),有pdb的話,可以看到函數(shù)名字,我們可以看出來32e3e858 是屬于下面這個(gè)函數(shù)調(diào)用的棧上的
32e3e9fc 2e3ae8d7 nqp+0x17bbd6
在有symbol的情況下,就可以知道是哪個(gè)函數(shù)了,在這個(gè)函數(shù)里面,找到了這樣的語句。
char memberDocURL[MAX_PATH+1] = "javascript:void(0);";
........
URLencodedString.Copy(dn); huURL.URLencode(&URLencodedString); strcat(memberDocURL, URLencodedString);
在棧上開了個(gè)MAX_PATH+1的數(shù)組,這里MAX_PATH是260,然后一系列操作,都是靠strcat來做的,dn這個(gè)字符串如果很長,就像上面顯示的那樣,URLencodedString也會(huì)很長,就會(huì)導(dǎo)致寫越界了。把后面的對象給覆蓋了。
到這里,原因就找到了。典型的字符串操作不小心導(dǎo)致緩存區(qū)溢出。
總結(jié)一下,對于這種不可重現(xiàn)的crash問題,就是要從crash的地方入手,看看哪個(gè)數(shù)據(jù)不對,順藤摸瓜,一點(diǎn)點(diǎn)往前面排查。windbg是個(gè)很強(qiáng)大的調(diào)試工具,特別是分析這種只有dump,沒法重現(xiàn)的問題,非常有用。
|