作者:tess
原文地址:ASP.NET Crash - Crazy looping in a SiteMap
本文只是一個快速的概括性翻譯, 如果敢興趣還請您看原版.
我想快速建立一個並不美觀的網站地圖(site map), 所以我只簡單的在我override的BuildSiteMap()函數裡使用一個小迴圈添加了幾個地圖節點(sitemap node)
public override SiteMapNode BuildSiteMap(){
for (int i = 0; i < 5; i++)
myRoot.ChildNodes.Add(new SiteMapNode(this, i.ToString(), i.ToString(), i.ToString()));
return myRoot;
}
當我運行這個web應用程式的時候我得到的卻是"堆疊溢位(Stack overflow)",然後伺服器就崩潰了. 然後我藉助調試器調試這段代碼,我所看到的東西非常奇怪:
1) int i = 0
2) i < 5
3) myRoot
4) int i = 0
5) i < 5
etc.
看起來i的值並沒有增加,是編譯器的bug還是CLR的?(暫時撇開sitemap的內部機制, 因為sitemap從設計上不允許我們這麼使用,但是這個條語句你卻可以隨便寫的)
在debug之前我們向後退幾步重審一下:
- 堆疊溢位
- 一個看起來像是無盡的迴圈
造成堆疊溢位是因為我們已經佔用了大量的記憶體,而它們是當初為了在棧上分配太多的指向指向局部變數或參數的指標而服務的.(we have exceeded the amount of memory reserved for the stack by allocating too many function pointers, pointers to local vars and parameters on the stack.)但是往往我們都是因為死迴圈(never-ending recursion)而造成的, 換言之, funcitonA()調用了functionB(),而functionB()裡又調用了functionA().
void MyRecursiveFunction(){
for(int i=0; i<5; i++){
--> MyRecursiveFunction();
}
}
所以此時我們的調用棧(callstack)看起來應該是這樣的:
functionB()
functionA()
functionB()
functionA()
好,那我們現在想象一下如果你現在有這樣的函數:
當你第一次在斷點位置上停止的時候i的值應該是0,調用棧看起來應該是
MyRecursiveFunction()
...
現在我們用另一種方式執行進入函數內部(其實還是它本身)
for(int i=0; i<5; i++){
for(int i2=0; i2<5; i2++){
for(int i3=0; i3<5; i3++){
for(int i4=0; i4<5; i4++){
for(int i5=0; i5<5; i5++){
for(int i6=0; i6<5; i6++){
for(int i7=0; i7<5; i7++){
}
}
}
}
}
}
}
所以調用棧應該是這樣的:
MyRecursiveFunction()
MyRecursiveFunction()
MyRecursiveFunction()
MyRecursiveFunction()
MyRecursiveFunction()
MyRecursiveFunction()
MyRecursiveFunction()
調試:
將windbg附屬到w3wp.exe的進程上(檔案/附屬到進程),按下g運行.程式一會就終止了顯示出了下列資訊,表明是堆疊溢位(就像我們已經知道的一樣)
(7e4.ddc): Stack overflow - code c00000fd (first chance)First chance exceptions are reported before any exception handling.This exception may be expected and handled.eax=0fa4235c ebx=02beca74 ecx=02beca74 edx=02becb54 esi=02becb54 edi=02beca74eip=686b5cb4 esp=02163000 ebp=02163004 iopl=0 nv up ei pl zr na pe nccs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00210246System_Web_ni+0xf5cb4:
686b5cb4 56 push esi
如果我們用!clrstack看一下堆棧來確定我們是怎麼結束的,我們只能看到這個:
0:016> !clrstackOS Thread Id: 0xddc (16)ESP EIP
02163000 686b5cb4 System.Web.StaticSiteMapProvider.GetChildNodes(System.Web.SiteMapNode)
可惜的這些不能告訴我們更多的東西, 當我們陷入堆疊溢位錯誤的時候!clrstack有時會在列舉堆棧資訊上出現一些問題,所以我們必須使用!dumpstack來看一下.
(注意:!dumpstack不展示真實的堆棧,有一些函數可能是錯誤的,但是它能很好的讓我們知道究竟怎麼了)
0:016> !dumpstackOS Thread Id: 0xddc (16)Current frame: (MethodDesc 0x68b03720 +0x4 System.Web.StaticSiteMapProvider.GetChildNodes(System.Web.SiteMapNode))ChildEBP RetAddr Caller,Callee02163004 686b1fc4 (MethodDesc 0x68aeff30 +0x18 System.Web.SiteMapNode.get_ChildNodes())0216300c 0f765641 (MethodDesc 0xfa42328 +0x59 ViewSiteMapProvider.BuildSiteMap())0216303c 686b5cdf (MethodDesc 0x68b03720 +0x2f System.Web.StaticSiteMapProvider.GetChildNodes(System.Web.SiteMapNode))02163074 686b1fc4 (MethodDesc 0x68aeff30 +0x18 System.Web.SiteMapNode.get_ChildNodes())0216307c 0f765641 (MethodDesc 0xfa42328 +0x59 ViewSiteMapProvider.BuildSiteMap())021630ac 686b5cdf (MethodDesc 0x68b03720 +0x2f System.Web.StaticSiteMapProvider.GetChildNodes(System.Web.SiteMapNode))021630e4 686b1fc4 (MethodDesc 0x68aeff30 +0x18 System.Web.SiteMapNode.get_ChildNodes())021630ec 0f765641 (MethodDesc 0xfa42328 +0x59 ViewSiteMapProvider.BuildSiteMap())0216311c 686b5cdf (MethodDesc 0x68b03720 +0x2f System.Web.StaticSiteMapProvider.GetChildNodes(System.Web.SiteMapNode))02163154 686b1fc4 (MethodDesc 0x68aeff30 +0x18 System.Web.SiteMapNode.get_ChildNodes())0216315c 0f765641 (MethodDesc 0xfa42328 +0x59 ViewSiteMapProvider.BuildSiteMap())...
Ok,看起來問題出在ChildNodes屬性上了, 它調用GetChildNodes()函數,而GetChildNodes()又再次調用我們的BuildSiteMap函數, 該函數調用了ChildNodes屬性, 所以這樣造成一個死迴圈.
結論:
在建立網站地圖一文中你可以找到答案和解決文章開始時問題的正確處理方法.