A not so common stack overflow
This is an ASP.Net 2.0 application experienced stack overflow on both X86 and X64. Same again, we captured crash dump file to find out the reason of stack overflow.
Here is the edited output of fault stack.
0:014> kL
ChildEBP RetAddr
01cd3d80 7a0363d7 kernel32!RaiseException+0x53
01cd3d9c 7a0c86ae mscorwks!ReportStackOverflow+0x61
01cd3dac 79e7c5d2 mscorwks!Alloc+0x3b
01cd3dec 79e7c697 mscorwks!FastAllocateObject+0x38
....
01cd3efc 14770201 System_Web_ni!System.Web.UI.TemplateControl.WriteUTF8ResourceString
01d214dc 660abfc1 wrong!ASP. default _aspx.__RenderContent
There was no deep recursive call, so it should due to someone reserved too much stack spaces. Look at the highlighted child EBPs, wrong!ASP. default _aspx.__RenderContent reserved huge spaces(316,896).
Here is the assembly of this methond:
wrong!ASP. default _aspx.__RenderContent
225d0258 55 push ebp
225d0259 8bec mov ebp,esp
225d025b 57 push edi
225d025c 56 push esi
225d025d 53 push ebx
//test the stack is enough or not, this loop test if stack has 4d5e0 space.
225d025e 33c0 xor eax,eax
225d0260 850404 test dword ptr [esp+eax],eax
225d0263 2d00100000 sub eax,1000h
225d0268 3d202afbff cmp eax, 0FFFB2A20h
225d026d 7df1 jge wrong!ASP.default _aspx.__RenderContent(...) +0x8 ( 225d0260 ) //Jump back to the test instruction
//Do the real stack reservation
225d026f 81ece0d50400 sub esp,4D5E0h
This is page generated by a tool with 5000+ child controls. I tried to review wrong!ASP.default _aspx.__RenderContent in reflector and it took 20+ minutes with 100% CPU. The code looks like below:
Try {
Control 1 do something.....
} catch (Exception ex)
{
.....
}
Try {
Control 2 do something.....
} catch (Exception ex2)
{
....
}
Try {
Control 5000 do something.....
} catch (Exception ex50000)
{
.......
}
And then, I was able to reproduce the problem with follow console code.
Try {
Console.WriteLine("hello wolrd");
} catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
.........
Try {
Console.WriteLine("hello wolrdxxx");
} catch (Exception exxxx)
{
Console.WriteLine(exxxx.Message);
}
Further testing shows the problem happens with debug flag only which means disabled optimization. With debug flag disabled, the code generated looks like this. It reserves very few stack space only.
052509b8 55 push ebp
052509b9 8bec mov ebp,esp
052509bb 57 push edi
052509bc 56 push esi
052509bd 53 push ebx
052509be 81ec98000000 sub esp,980h
052509c4 33c0 xor eax,eax
052509c6 8945e0 mov dword ptr [ebp-20h],eax
So, the problem was resolved after disabled the debug flag on X86 system, however it doesn't work on X64 system with 64bit process.
I did some tests using exactly same code and found the problem happens with 64bit process only. Below table shows the stack size reserved with and without debug flag on different platform. It shows there is no big difference between debug and release mode for 64bit process on 64bit system.
Debug Flag |
W2k3 32bit |
W2k8 32bit |
W2k8 64bit/64bit process |
W2k8 64bit/32bit process |
false |
0x5dd4(24,020) |
0x5dd4 |
0x4d5e0 |
0x5dd4 |
true |
0x1d4a4(120,138) |
0x1d4a4 |
0x4e1a0 |
0x1d4a4 |
Another problem I noticed is 64bit process takes couple of minutes for JIT while 32bit system takes couple of seconds only.
Currently, Microsoft is working on this X64 optimization problem. We can work around the problem by:
- 1. Reduce the page size generated by the tool
- 2. Using a 32bit process with debug flag disabled.
See you next time,
Wei