StackOverflowException이 throw 될 때 .NET이 그렇게 제대로 작동하지 않는 이유는 무엇입니까?
.NET의 StackOverflowExceptions는 잡히지 않고 프로세스를 중단하며 스택 추적이 없다는 것을 알고 있습니다. 이것은 공식적 으로 MSDN에 문서화 되어 있습니다. 그러나 나는 기술적 (또는 다른) 이유가 행동의 배후에 있는지 궁금합니다. 모든 MSDN은 다음과 같이 말합니다.
이전 버전의 .NET Framework에서는 애플리케이션이 StackOverflowException 개체를 포착 할 수있었습니다 (예 : 무제한 재귀에서 복구하기 위해). 그러나 스택 오버플로 예외를 안정적으로 포착하고 프로그램 실행을 계속하려면 상당한 추가 코드가 필요하기 때문에이 방법은 현재 권장되지 않습니다.
이 "중요한 추가 코드"는 무엇입니까? 이 동작에 대한 다른 문서화 된 이유가 있습니까? SOE를 잡을 수 없더라도 적어도 스택 추적을 얻을 수없는 이유는 무엇입니까? 여러 동료와 저는 스택 추적으로 몇 분이 걸리던 프로덕션 StackOverflowException을 디버깅하는 데 몇 시간을 투자했습니다. 그래서 제 고통에 대한 좋은 이유가 있는지 궁금합니다.
스레드 스택은 Windows에서 생성됩니다. 소위 가드 페이지 를 사용 하여 스택 오버플로를 감지 할 수 있습니다. 이 MSDN Library 문서에 설명 된대로 사용자 모드 코드에서 일반적으로 사용할 수있는 기능입니다 . 기본 아이디어는 스택의 마지막 두 페이지 (2 x 4096 = 8192 바이트)가 예약되어 있고 이에 대한 프로세서 액세스가 SEH 예외 인 STATUS_GUARD_PAGE_VIOLATION으로 바뀌는 페이지 오류를 트리거한다는 것입니다.
이것은 스레드 스택에 속하는 페이지의 경우 커널에 의해 차단됩니다. 두 페이지 중 첫 번째 페이지의 보호 속성을 변경하여 스레드에 사고를 처리 할 수있는 비상 스택 공간을 제공 한 다음 STATUS_STACK_OVERFLOW 예외를 다시 발생시킵니다.
이 예외는 차례로 CLR에 의해 차단됩니다. 이 시점에서 약 3KB의 스택 공간이 남아 있습니다. 이것은 프로그램에서 예외를 처리 할 수있는 코드를 컴파일하기 위해 Just-in-time 컴파일러 (JITter) 를 실행하기에 충분하지 않기 때문에 JITter는 그보다 훨씬 더 많은 공간이 필요합니다. 따라서 CLR은 다른 작업을 수행 할 수 없지만 무례하게 스레드를 중단합니다. 또한 프로세스를 종료하는 .NET 2.0 정책에 의해.
이것이 Java에서는 문제가되지 않고 바이트 코드 인터프리터가 있으므로 실행 가능한 사용자 코드가 실행될 수 있음을 보장합니다. 또는 C, C ++ 또는 Delphi와 같은 언어로 작성된 관리되지 않는 프로그램에서 빌드시 코드가 생성됩니다. 그러나 여전히 처리하기 매우 어려운 사고이며 스택의 비상 공간이 날아가므로 스레드에서 코드를 계속 실행하는 것이 안전한 시나리오는 없습니다. 스레드가 완전히 임의의 위치에서 중단되고 오히려 손상된 상태로 프로그램이 올바르게 계속 작동 할 가능성은 거의 없습니다.
다른 스레드에서 이벤트를 발생 시키거나 winapi (가드 페이지 수를 구성 할 수 없음)에서 제한을 제거하는 데 전혀 노력이 없었다면 이는 매우 잘 보존 된 비밀이거나 유용하지 않은 것으로 간주됩니다. 나는 후자를 의심하지만 사실은 모릅니다.
스택은 프로그램 상태에 대한 거의 모든 것이 저장되는 곳입니다. 메서드가 호출 될 때 각 반환 사이트의 주소, 지역 변수, 메서드 매개 변수 등. 메서드 가 스택을 오버플로 하는 경우 실행을 계속하려면 스택 공간이 더 이상 남아 있지 않으므로 필요에 따라 즉시 실행을 중지해야합니다. . 그런 다음 정상적으로 복구하려면 누군가 가 죽기 전에 해당 메서드가 스택에 수행 한 모든 작업 을 정리 해야합니다 . 이것은 메서드가 호출되기 전에 스택이 어떻게 생겼는지 아는 것을 의미합니다. 이로 인해 약간의 오버 헤드가 발생합니다.
스택을 정리할 수없는 경우 스택 추적도 얻을 수 없습니다. 추적을 생성하는 데 필요한 정보는 스택을 "언 롤링"하여 호출 된 메서드를 발견하기 때문입니다.
스택 오버플로 또는 메모리 부족 상태를 정상적으로 처리하려면 스택이 실제로 오버플로되거나 힙 메모리가 완전히 고갈되기 전에 예외를 트리거해야합니다. 이때 사용 가능한 스택 및 힙 리소스가 실행하기에 충분할 때 예외가 발견되기 전에 실행해야하는 정리 코드. 스택 오버플로 예외의 경우 깔끔하게 처리하려면 기본적으로 각 메서드에 대한 진입시 스택 포인터를 확인해야합니다 (실제로 비용이 많이 들지는 않습니다). 일반적으로 스택 끝 바로 너머에 액세스 위반 트랩을 설정하여 처리되지만 문제는 이미 너무 늦어서 작업을 깔끔하게 처리 할 수 없을 때까지 트랩이 실행되지 않는다는 것입니다. 과거가 아닌 스택의 마지막 메모리 블록에서 트랩을 실행하도록 설정할 수 있습니다.StackOverflowException
,하지만 문제는 스택이 그 정도까지 풀리면 "거의 스택 부족"트랩을 다시 활성화 할 수있는 좋은 방법이 없다는 것입니다.
즉, 대체 접근 방식은 스레드가 스택을 날릴 경우 발생해야 할 일에 대해 스레드가 델리게이트를 설정하도록 허용 한 다음 StackOverflowException
스레드의 스택이 지워지고 제공된 델리게이트를 실행할 것이라고 말하는 것입니다 . 델리게이트를 실행하기 전에 트랩을 복원 할 수 있으며 (스택은 해당 지점에서 비어 있음) 코드는 델리게이트가 중요한 finally
블록을 건너 뛰었 는지 여부를 알기 위해 사용할 수있는 스레드 상태 개체를 유지할 수 있습니다 .
'developer tip' 카테고리의 다른 글
Bootstrap 3을 사용하는 Twitter Typeahead의 CSS 문제 (0) | 2020.12.08 |
---|---|
Ajax 업로드 이미지 (0) | 2020.12.08 |
Jasmine 2.0 비동기 done () 및 angular-mocks inject () 동일한 테스트 it () (0) | 2020.12.08 |
도커 이미지가 도커에서 사용하지 않는 디스크 공간을 차지하는 이유 (0) | 2020.12.08 |
HEAD 기록에서 업스트림 SVN 정보를 확인할 수 없습니다. (0) | 2020.12.08 |