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