Se lanzan excepciones StackOverFlow cuando se manejan excepciones en llamadas recursivas en procesos en arquitecturas x64
El breve título de este post se debe a que hace unas semanas tuve un caso en el que me ayudo mi compañero Helge Mahrt (os dejo aquí su blog https://blogs.msdn.com/b/hmahrt/) en el que veíamos un comportamiento diferente entre aplicaciones de .NET de 32 o 64 bits cuando se tratan excepciones en una función que se invoca recursivamente y que esto puede llevar a un desbordamiento en el Stack.
En este artículo podemos ver cómo se manejan las excepciones en las arquitecturas x64 https://msdn.microsoft.com/es-es/library/ms235286(v=VS.80).aspx:
Dado el conjunto de registros ampliado, x64 simplemente utiliza la convención de llamada __fastcall y un modelo del control de excepciones basado en RISC.
Aquí tenemos otros artículos en los que se describe el Structured Exception Handling (SEH):
· SEH en x86: https://msdn.microsoft.com/es-es/library/ms253960(v=VS.80).aspx
· SEH en RISC: https://msdn.microsoft.com/es-es/library/ms253511(v=VS.80).aspx (Este es más detallado pero es para WinCE: https://msdn.microsoft.com/en-us/library/ms925504.aspx)
Si estamos controlando una excepción dentro de una llamada recursive tendremos un stack parecido a esto:
0:000> kL
Child-SP RetAddr Call Site
00000000`005d4350 000007ff`95624d41 UnWind!NDP_UE_CS.NestedExceptions.Main()+0x18f
00000000`005d4390 000007ff`9556643e mscorwks!ExceptionTracker::CallHandler+0x145
00000000`005d4490 000007ff`955b2c8b mscorwks!ExceptionTracker::CallCatchHandler+0x9e
00000000`005d4520 000007ff`b3c946cd mscorwks!ProcessCLRException+0x2ab
00000000`005d45c0 000007ff`b3c94d3a ntdll!zzz_AsmCodeRange_End
00000000`005d45f0 000007ff`9553fafa ntdll!RtlUnwindEx+0x425
00000000`005d4cd0 000007ff`955b2c37 mscorwks!ClrUnwindEx+0x36
00000000`005d51e0 000007ff`b3c9464d mscorwks!ProcessCLRException+0x257
00000000`005d5280 000007ff`b3c9567c ntdll!RtlpExecuteHandlerForException+0xd
00000000`005d52b0 000007ff`b3c74bba ntdll!RtlDispatchException+0x392
00000000`005d59c0 000007ff`b0d689cc ntdll!KiUserExceptionDispatch+0x2e
00000000`005d60c0 000007ff`956398ff KERNELBASE!RaiseException+0x68
00000000`005d61a0 000007ff`95b24350 mscorwks!RaiseTheExceptionInternalOnly+0x2ff
00000000`005d6290 000007ff`35e103f1 mscorwks!JIT_Throw+0x130
00000000`005d6440 000007ff`95624d41 UnWind!NDP_UE_CS.NestedExceptions.DivideBy0(Int32)+0x101
00000000`005d6490 000007ff`9556643e mscorwks!ExceptionTracker::CallHandler+0x145
00000000`005d6590 000007ff`955b2c8b mscorwks!ExceptionTracker::CallCatchHandler+0x9e
00000000`005d6620 000007ff`b3c946cd mscorwks!ProcessCLRException+0x2ab
00000000`005d66c0 000007ff`b3c94d3a ntdll!zzz_AsmCodeRange_End
00000000`005d66f0 000007ff`9553fafa ntdll!RtlUnwindEx+0x425
00000000`005d6dd0 000007ff`955b2c37 mscorwks!ClrUnwindEx+0x36
00000000`005d72e0 000007ff`b3c9464d mscorwks!ProcessCLRException+0x257
00000000`005d7380 000007ff`b3c9567c ntdll!RtlpExecuteHandlerForException+0xd
00000000`005d73b0 000007ff`b3c74bba ntdll!RtlDispatchException+0x392
00000000`005d7ac0 000007ff`b0d689cc ntdll!KiUserExceptionDispatch+0x2e
00000000`005d81c0 000007ff`956398ff KERNELBASE!RaiseException+0x68
00000000`005d82a0 000007ff`95b24350 mscorwks!RaiseTheExceptionInternalOnly+0x2ff
00000000`005d8390 000007ff`35e103f1 mscorwks!JIT_Throw+0x130
00000000`005d8540 000007ff`95624d41 UnWind!NDP_UE_CS.NestedExceptions.DivideBy0(Int32)+0x101
00000000`005d8590 000007ff`9556643e mscorwks!ExceptionTracker::CallHandler+0x145
00000000`005d8690 000007ff`955b2c8b mscorwks!ExceptionTracker::CallCatchHandler+0x9e
00000000`005d8720 000007ff`b3c946cd mscorwks!ProcessCLRException+0x2ab
00000000`005d87c0 000007ff`b3c94d3a ntdll!zzz_AsmCodeRange_End
00000000`005d87f0 000007ff`9553fafa ntdll!RtlUnwindEx+0x425
00000000`005d8ed0 000007ff`955b2c37 mscorwks!ClrUnwindEx+0x36
00000000`005d93e0 000007ff`b3c9464d mscorwks!ProcessCLRException+0x257
00000000`005d9480 000007ff`b3c9567c ntdll!RtlpExecuteHandlerForException+0xd
00000000`005d94b0 000007ff`b3c74bba ntdll!RtlDispatchException+0x392
00000000`005d9bc0 000007ff`b0d689cc ntdll!KiUserExceptionDispatch+0x2e
00000000`005da2c0 000007ff`956398ff KERNELBASE!RaiseException+0x68
00000000`005da3a0 000007ff`95b24350 mscorwks!RaiseTheExceptionInternalOnly+0x2ff
00000000`005da490 000007ff`35e103f1 mscorwks!JIT_Throw+0x130
00000000`005da640 000007ff`95624d41 UnWind!NDP_UE_CS.NestedExceptions.DivideBy0(Int32)+0x101
00000000`005da690 000007ff`9556643e mscorwks!ExceptionTracker::CallHandler+0x145
00000000`005da790 000007ff`955b2c8b mscorwks!ExceptionTracker::CallCatchHandler+0x9e
00000000`005da820 000007ff`b3c946cd mscorwks!ProcessCLRException+0x2ab
00000000`005da8c0 000007ff`b3c94d3a ntdll!zzz_AsmCodeRange_End
00000000`005da8f0 000007ff`9553fafa ntdll!RtlUnwindEx+0x425
00000000`005dafd0 000007ff`955b2c37 mscorwks!ClrUnwindEx+0x36
00000000`005db4e0 000007ff`b3c9464d mscorwks!ProcessCLRException+0x257
00000000`005db580 000007ff`b3c9567c ntdll!RtlpExecuteHandlerForException+0xd
00000000`005db5b0 000007ff`b3c74bba ntdll!RtlDispatchException+0x392
00000000`005dbcc0 000007ff`b0d689cc ntdll!KiUserExceptionDispatch+0x2e
00000000`005dc3c0 000007ff`956398ff KERNELBASE!RaiseException+0x68
00000000`005dc4a0 000007ff`95b24350 mscorwks!RaiseTheExceptionInternalOnly+0x2ff
00000000`005dc590 000007ff`35e103f1 mscorwks!JIT_Throw+0x130
00000000`005dc740 000007ff`95624d41 UnWind!NDP_UE_CS.NestedExceptions.DivideBy0(Int32)+0x101
00000000`005dc790 000007ff`9556643e mscorwks!ExceptionTracker::CallHandler+0x145
00000000`005dc890 000007ff`955b2c8b mscorwks!ExceptionTracker::CallCatchHandler+0x9e
00000000`005dc920 000007ff`b3c946cd mscorwks!ProcessCLRException+0x2ab
00000000`005dc9c0 000007ff`b3c94d3a ntdll!zzz_AsmCodeRange_End
00000000`005dc9f0 000007ff`9553fafa ntdll!RtlUnwindEx+0x425
00000000`005dd0d0 000007ff`955b2c37 mscorwks!ClrUnwindEx+0x36
00000000`005dd5e0 000007ff`b3c9464d mscorwks!ProcessCLRException+0x257
00000000`005dd680 000007ff`b3c9567c ntdll!RtlpExecuteHandlerForException+0xd
00000000`005dd6b0 000007ff`b3c74bba ntdll!RtlDispatchException+0x392
00000000`005dddc0 000007ff`b0d689cc ntdll!KiUserExceptionDispatch+0x2e
00000000`005de4c0 000007ff`956ddc3c KERNELBASE!RaiseException+0x68
00000000`005de5a0 000007ff`956ddc8a mscorwks!NakedThrowHelper2+0xc
00000000`005de5d0 000007ff`956ddc95 mscorwks!NakedThrowHelper_RspAligned+0x3d
00000000`005deb48 000007ff`35e10373 mscorwks!NakedThrowHelper_FixRsp+0x5
00000000`005deb50 000007ff`35e10361 UnWind!NDP_UE_CS.NestedExceptions.DivideBy0(Int32)+0x83
00000000`005debd0 000007ff`35e10361 UnWind!NDP_UE_CS.NestedExceptions.DivideBy0(Int32)+0x71
00000000`005dec50 000007ff`35e10361 UnWind!NDP_UE_CS.NestedExceptions.DivideBy0(Int32)+0x71
00000000`005decd0 000007ff`35e10189 UnWind!NDP_UE_CS.NestedExceptions.DivideBy0(Int32)+0x71
00000000`005ded50 000007ff`956dd562 UnWind!NDP_UE_CS.NestedExceptions.Main()+0x69
00000000`005dede0 000007ff`9561a293 mscorwks!CallDescrWorker+0x82
00000000`005dee20 000007ff`95abc7bf mscorwks!CallDescrWorkerWithHandler+0xd3
00000000`005deec0 000007ff`9554409b mscorwks!MethodDesc::CallDescr+0x2af
00000000`005df100 000007ff`9552019c mscorwks!ClassLoader::RunMain+0x22b
00000000`005df360 000007ff`95ba8e2d mscorwks!Assembly::ExecuteMainMethod+0xbc
00000000`005df650 000007ff`95512fd7 mscorwks!SystemDomain::ExecuteMainMethod+0x47d
00000000`005dfc20 000007ff`95530430 mscorwks!ExecuteEXE+0x47
00000000`005dfc70 000007ff`a75874e5 mscorwks!_CorExeMain+0xac
00000000`005dfcd0 000007ff`a7635b21 mscoreei!_CorExeMain+0xe0
00000000`005dfd20 000007ff`b31c167e MSCOREE!_CorExeMain_Exported+0x57
00000000`005dfd50 000007ff`b3c93501 KERNEL32!BaseThreadInitThunk+0x1a
00000000`005dfd80 00000000`00000000 ntdll!RtlUserThreadStart+0x1d
Lo que stamos viendo es el virtual unwinding del stack: https://msdn.microsoft.com/en-us/library/ms253605(v=vs.80).aspx: “In virtual unwinding, the kernel traverses the call stack to find an appropriate exception handler.”
Esto puede dar lugar a que se llene el stack. Sin embargo, si se gestiona la excepción fuera de las funciones recursivas el stack tendría este aspecto:
0:000> kL
Child-SP RetAddr Call Site
00000000`00f8c610 000007ff`95624d41 UnWind!NDP_UE_CS.NestedExceptions.Main()+0x18f
00000000`00f8c650 000007ff`9556643e mscorwks!ExceptionTracker::CallHandler+0x145
00000000`00f8c750 000007ff`955b2c8b mscorwks!ExceptionTracker::CallCatchHandler+0x9e
00000000`00f8c7e0 000007ff`b3c946cd mscorwks!ProcessCLRException+0x2ab
00000000`00f8c880 000007ff`b3c94d3a ntdll!zzz_AsmCodeRange_End
00000000`00f8c8b0 000007ff`9553fafa ntdll!RtlUnwindEx+0x425
00000000`00f8cf90 000007ff`955b2c37 mscorwks!ClrUnwindEx+0x36
00000000`00f8d4a0 000007ff`b3c9464d mscorwks!ProcessCLRException+0x257
00000000`00f8d540 000007ff`b3c9567c ntdll!RtlpExecuteHandlerForException+0xd
00000000`00f8d570 000007ff`b3c74bba ntdll!RtlDispatchException+0x392
00000000`00f8dc80 000007ff`b0d689cc ntdll!KiUserExceptionDispatch+0x2e
00000000`00f8e3a0 000007ff`956ddc3c KERNELBASE!RaiseException+0x68
00000000`00f8e480 000007ff`956ddc8a mscorwks!NakedThrowHelper2+0xc
00000000`00f8e4b0 000007ff`956ddc95 mscorwks!NakedThrowHelper_RspAligned+0x3d
00000000`00f8ea28 000007ff`35e2036b mscorwks!NakedThrowHelper_FixRsp+0x5
00000000`00f8ea30 000007ff`35e20358 UnWind!NDP_UE_CS.NestedExceptions.DivideBy0(Int32)+0x7b
00000000`00f8ea80 000007ff`35e20358 UnWind!NDP_UE_CS.NestedExceptions.DivideBy0(Int32)+0x68
00000000`00f8ead0 000007ff`35e20358 UnWind!NDP_UE_CS.NestedExceptions.DivideBy0(Int32)+0x68
00000000`00f8eb20 000007ff`35e20189 UnWind!NDP_UE_CS.NestedExceptions.DivideBy0(Int32)+0x68
00000000`00f8eb70 000007ff`956dd562 UnWind!NDP_UE_CS.NestedExceptions.Main()+0x69
00000000`00f8ec00 000007ff`9561a293 mscorwks!CallDescrWorker+0x82
00000000`00f8ec40 000007ff`95abc7bf mscorwks!CallDescrWorkerWithHandler+0xd3
00000000`00f8ece0 000007ff`9554409b mscorwks!MethodDesc::CallDescr+0x2af
00000000`00f8ef20 000007ff`9552019c mscorwks!ClassLoader::RunMain+0x22b
00000000`00f8f180 000007ff`95ba8e2d mscorwks!Assembly::ExecuteMainMethod+0xbc
00000000`00f8f470 000007ff`95512fd7 mscorwks!SystemDomain::ExecuteMainMethod+0x47d
00000000`00f8fa40 000007ff`95530430 mscorwks!ExecuteEXE+0x47
00000000`00f8fa90 000007ff`a75874e5 mscorwks!_CorExeMain+0xac
00000000`00f8faf0 000007ff`a7635b21 mscoreei!_CorExeMain+0xe0
00000000`00f8fb40 000007ff`b31c167e MSCOREE!_CorExeMain_Exported+0x57
00000000`00f8fb70 000007ff`b3c93501 KERNEL32!BaseThreadInitThunk+0x1a
00000000`00f8fba0 00000000`00000000 ntdll!RtlUserThreadStart+0x1d
A simple vista podemos ver que el tamaño de un stack y del otro es visiblemente inferior, más teniendo en cuenta que el stack que controla las excepciones dentro de las funciones recursivas irá creciendo dependiendo del número de llamadas recursivas que se hayan producido.
Como recomendación general os diría que se revise el uso de llamadas recursivas y en el caso de estar en 64 bits, el manejar las excepciones fuera de las llamadas recursivas.
Espero que os haya servido de ayuda
- José Ortega Gutiérrez