MSDN.WhiteKnight - Stack Overflow answers
Ответ на "Почему переполнение стека в дочернем потоке убивает весь процесс?"
Answer 908020
В С++- это обрабатываемое исключение, если верить комментариям из предыдущего вопроса
На самом деле все немного не так. Стандартными средствами С++, разумеется, нельзя обработать переполнение стека. Однако, в Windows его можно обработать с помощью механизма SEH. И, что бы ни говорил Эрик Липперт, восстановление после переполнения стека - вполне поддерживаемый сценарий, иначе зачем бы существовали функции _resetstkoflw и SetThreadStackGuarantee?
а в .NET нет, так как возможно, как я понимаю, что СLR, которой нужно что-то сделать, может повредится из-за не хватки стека
В .NET StackOverflowException не обрабатывается не потому, что это технически невозможно, а потому, что так решили разработчики. В Windows переполнение стека порождает исключение SEH с кодом STATUS_STACK_OVERFLOW (0xC00000FD). CLR перехватывает SEH-исключения и, если видит этот код, принудительно убивает процесс (будучи загруженной с параметрами по умолчанию). При этом куда более опасное Access Violation .NET почему-то разрешает обрабатывать.
Получается, что запуская чужой код, к исходникам которого мы не имеем доступа, хоть в отдельном потоке, хоть в отдельном домене, то мы все равно падаем и в .NET никак нельзя предотвратить это при таком типе исключения?
Только средствами .NET нельзя. Однако в неуправляемом коде нужно написать, по сути, очень немного.
Один из способов обойти это поведение, это создать специальную неуправляемую DLL, единственной целью которой будет обработать SEH-исключение и поменять его код на тот, который CLR "не напугает" (SEH-исключения с неизвестным кодом CLR преобразует в SEHException, которое можно обработать). В приложении на C# загрузить DLL, установить векторный обработчик исключений и увеличить размер зарезервированной области стека с помощью функции SetThreadStackGuarantee.
Конечно, это не обеспечит полное восстановление стека, т.е., чтобы можно было далее в том же потоке снова словить переполнение стека и обработать его. Но если просто позволить потоку завершиться и забыть про него, это не имеет значения: вновь созданные потоки уже будут иметь корректный стек.
Например, создадим DLL на С++ с таким кодом:
#include <malloc.h> #include <windows.h> #ifdef __cplusplus extern "C"{ #endif __declspec(dllexport) LONG WINAPI fnCrashHandler(LPEXCEPTION_POINTERS pExceptionInfo) { if(pExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_STACK_OVERFLOW){ pExceptionInfo->ExceptionRecord->ExceptionCode = 0x1234; } return EXCEPTION_CONTINUE_SEARCH; } #ifdef __cplusplus } #endif
Назовем ее, допустим, CrashHandler.dll, и поместим в каталог с программой. Тогда в C# можно обработать переполнение стека таким образом:
using System; using System.Collections.Generic; using System.Text; using System.Threading; using System.Runtime.InteropServices; namespace ConsoleTest { class Program { [DllImport("kernel32.dll")] public static extern IntPtr AddVectoredExceptionHandler( uint FirstHandler, IntPtr VectoredHandler ); [DllImport("kernel32.dll")] public static extern int SetThreadStackGuarantee( ref uint StackSizeInBytes); [DllImport("kernel32.dll")] public static extern IntPtr LoadLibrary([MarshalAs(UnmanagedType.LPStr)]string lpFileName); [DllImport("kernel32.dll", CharSet = CharSet.Ansi, ExactSpelling = true)] public static extern IntPtr GetProcAddress(IntPtr hModule, string procName); static void Recursive() { Recursive(); } static void Test() { //увеличим размер зарезервированной области стека (30 KB должно быть достаточно) uint size = 30000; SetThreadStackGuarantee(ref size); try { Recursive(); } catch (SEHException) { Console.WriteLine("SEHException. Code: 0x" + Marshal.GetExceptionCode().ToString("X")); } } static void Main(string[] args) { //добавим обработчик исключений IntPtr h = LoadLibrary("CrashHandler.dll"); IntPtr fnAddress = GetProcAddress(h, "_fnCrashHandler@4"); //декорированное имя функции по правилам stdcall AddVectoredExceptionHandler(1, fnAddress); //запустим поток Thread thread = new Thread(Test); thread.Start(); thread.Join(); Console.WriteLine("Press any key..."); Console.ReadKey(); } } }
Примечание. Целевая архитектура неуправляемой DLL и приложения должны совпадать. Для AnyCPU-приложений понадобится иметь несколько неуправляемых DLL под каждую архитектуру и загружать нужную в зависимости от текущей архитектуры приложения.
Content is retrieved from StackExchange API.
Auto-generated by ruso-archive tools.