재귀 호출이 다른 스택 깊이에서 StackOverflow를 발생시키는 이유는 무엇입니까?
나는 C # 컴파일러가 테일 호출을 처리하는 방법을 실습하려고했습니다.
(답변 : 그렇지 않습니다. 그러나 64 비트 JIT 는 TCE (테일 콜 제거)를 수행합니다. 제한 사항이 적용됩니다 .)
그래서 StackOverflowException
프로세스를 죽이기 전에 호출되는 횟수를 출력하는 재귀 호출을 사용하여 작은 테스트를 작성했습니다 .
class Program
{
static void Main(string[] args)
{
Rec();
}
static int sz = 0;
static Random r = new Random();
static void Rec()
{
sz++;
//uncomment for faster, more imprecise runs
//if (sz % 100 == 0)
{
//some code to keep this method from being inlined
var zz = r.Next();
Console.Write("{0} Random: {1}\r", sz, zz);
}
//uncommenting this stops TCE from happening
//else
//{
// Console.Write("{0}\r", sz);
//}
Rec();
}
큐에서 프로그램은 다음 중 하나에서 SO 예외로 끝납니다.
- '빌드 최적화'끄기 (디버그 또는 릴리스)
- 대상 : x86
- 대상 : AnyCPU + "Prefer 32 bit"(VS 2012의 새로운 기능이며 처음으로 보았습니다. 자세한 내용은 여기 )
- 코드에서 겉보기에 무해한 분기 (주석 'else'분기 참조).
반대로 '빌드 최적화'ON + (대상 = x64 또는 '32 비트 선호 '가 OFF 인 AnyCPU (64 비트 CPU에서))를 사용하면 TCE가 발생하고 카운터가 계속 회전합니다 (좋아요, 값이 오버플 로 될 때마다 회전이 감소 합니다. ).
하지만 내가 설명 할 수없는 행동주의 에 StackOverflowException
(?)이 결코에서 발생하지 : 경우 정확히 같은 스택 깊이를. 다음은 몇 가지 32 비트 실행, 릴리스 빌드의 출력입니다.
51600 Random: 1778264579
Process is terminated due to StackOverflowException.
51599 Random: 1515673450
Process is terminated due to StackOverflowException.
51602 Random: 1567871768
Process is terminated due to StackOverflowException.
51535 Random: 2760045665
Process is terminated due to StackOverflowException.
그리고 디버그 빌드 :
28641 Random: 4435795885
Process is terminated due to StackOverflowException.
28641 Random: 4873901326 //never say never
Process is terminated due to StackOverflowException.
28623 Random: 7255802746
Process is terminated due to StackOverflowException.
28669 Random: 1613806023
Process is terminated due to StackOverflowException.
스택 크기는 일정합니다 ( 기본값은 1MB ). 스택 프레임의 크기는 일정합니다.
그렇다면 StackOverflowException
히트가 발생했을 때 스택 깊이의 (때로는 사소하지 않은) 변화를 설명 할 수있는 것은 무엇입니까?
최신 정보
Hans Passant는 Console.WriteLine
P / Invoke, interop 및 비 결정적 잠금 문제를 제기합니다 .
그래서 코드를 다음과 같이 단순화했습니다.
class Program
{
static void Main(string[] args)
{
Rec();
}
static int sz = 0;
static void Rec()
{
sz++;
Rec();
}
}
디버거없이 Release / 32bit / Optimization ON에서 실행했습니다. 프로그램이 충돌하면 디버거를 연결하고 카운터 값을 확인합니다.
그리고 여러 번 실행 해도 여전히 동일하지 않습니다. (또는 내 테스트에 결함이 있습니다.)
업데이트 : 폐쇄
fejesjoco가 제안한대로 ASLR (Address space layout randomization)을 조사했습니다.
이는 스택 위치 및 크기를 포함하여 프로세스 주소 공간에서 다양한 항목을 무작위로 지정하여 버퍼 오버플로 공격이 특정 시스템 호출의 정확한 위치를 찾기 어렵게하는 보안 기술입니다.
이론은 좋은 것 같습니다. 연습 해 봅시다!
이를 테스트하기 위해 EMET 또는 The Enhanced Mitigation Experience Toolkit 작업에 특정한 Microsoft 도구를 사용했습니다 . 시스템 또는 프로세스 수준에서 ASLR 플래그 (및 훨씬 더)를 설정할 수 있습니다.
( 내가 시도하지 않은 시스템 전체의 레지스트리 해킹 대안도 있습니다 )
도구의 효율성을 확인하기 위해 Process Explorer 가 프로세스 의 '속성'페이지에서 ASLR 플래그의 상태를 정식으로보고 한다는 사실도 발견했습니다 . 오늘까지 본 적이 없습니다 :)
이론적으로 EMET는 단일 프로세스에 대해 ASLR 플래그를 (재) 설정할 수 있습니다. 실제로는 아무것도 변경하지 않은 것 같습니다 (위 이미지 참조).
However, I disabled ASLR for the entire system and (one reboot later) I could finally verify that indeed, the SO exception now always happens at the same stack depth.
BONUS
ASLR-related, in older news: How Chrome got pwned
I think it may be ASLR at work. You can turn off DEP to test this theory.
See here for a C# utility class to check memory information: https://stackoverflow.com/a/8716410/552139
By the way, with this tool, I found that the difference between the maximum and minimum stack size is around 2 KiB, which is half a page. That's weird.
Update: OK, now I know I'm right. I followed up on the half-page theory, and found this doc that examines the ASLR implementation in Windows: http://www.symantec.com/avcenter/reference/Address_Space_Layout_Randomization.pdf
Quote:
Once the stack has been placed, the initial stack pointer is further randomized by a random decremental amount. The initial offset is selected to be up to half a page (2,048 bytes)
And this is the answer to your question. ASLR takes away between 0 and 2048 bytes of your initial stack randomly.
Change r.Next()
to r.Next(10)
. StackOverflowException
s should occur in the same depth.
Generated strings should consume the same memory because they have the same size. r.Next(10).ToString().Length == 1
always. r.Next().ToString().Length
is variable.
The same applies if you use r.Next(100, 1000)
'programing tip' 카테고리의 다른 글
링크 오류 " '__gxx_personality_v0'에 대한 정의되지 않은 참조"및 g ++ (0) | 2020.10.26 |
---|---|
IllegalStateException의 용도는 무엇입니까? (0) | 2020.10.25 |
오프라인 iOS 웹 앱 : 매니페스트를로드하지만 오프라인에서 작동하지 않습니다. (0) | 2020.10.25 |
바퀴를 재발 명하지 않고 REST API 보안 (0) | 2020.10.25 |
Dockerfile에서 VOLUME의 목적은 무엇입니까 (0) | 2020.10.25 |