성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] Detecting blocking calls using asyn...
[정성태] 아쉽게도, 커뮤니티는 아니고 개인 블로그입니다. ^^
[정성태] 질문이 잘 이해가 안 됩니다. 우선, 해당 소스코드에서 ILis...
[양승조
] var대신 dinamic으로 선언해서 해결은 했습니다. 맞는 해...
[양승조
] 또 막혔습니다. ㅠㅠ var list = props[i].Ge...
[양승조
] 아. 감사합니다. 어제는 안됐던것 같은데....정신을 차려야겠네...
[정성태] "props[i].GetValue(props[i])" 코드에서 ...
[정성태] 저렇게 조각 코드 말고, 실제로 재현이 되는 예제 프로젝트를 압...
[정성태] Modules 창(Ctrl+Shift+U)을 띄워서, 해당 Op...
[정성태] 만드실 수 있습니다. 단지, Unity 엔진 내의 스크립트와 W...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <h1 style='font-family: Malgun Gothic, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>C# - 스택 메모리에 대한 여유 공간 확인하는 방법</h1> <p> 이에 대해서는 다음의 문서에서 힌트를 얻을 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Determining approximately how much stack space is available, part 1 ; <a target='tab' href='https://devblogs.microsoft.com/oldnewthing/20200609-00/?p=103847'>https://devblogs.microsoft.com/oldnewthing/20200609-00/?p=103847</a> Determining approximately how much stack space is available, part 2 ; <a target='tab' href='https://devblogs.microsoft.com/oldnewthing/20200610-00/?p=103855'>https://devblogs.microsoft.com/oldnewthing/20200610-00/?p=103855</a> </pre> <br /> 간단하게 정리해 볼까요? ^^<br /> <br /> 우선, <a target='tab' href='https://devblogs.microsoft.com/oldnewthing/20200609-00/?p=103847'>part 1</a>에서는 다음의 코드로 설명하고 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > __declspec(noinline) bool is_stack_available(size_t amount) { __try { <span style='color: blue; font-weight: bold'>_alloca(amount);</span> // 스택 할당 시도 후, return true; } __except ( GetExceptionCode() == EXCEPTION_STACK_OVERFLOW ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) { <span style='color: blue; font-weight: bold'>_resetstkoflw();</span> // EXCEPTION_STACK_OVERFLOW가 발생하면 _resetstkoflw 처리 return false; } } </pre> <br /> 위의 방법은, 어쨌든 EXCEPTION_STACK_OVERFLOW에 대한 처리는 할 수 있지만 아쉽게도 스택의 reserve 메모리 영역이 모두 commit된다는 차이점이 있습니다. 실제로 다음과 같이 간단하게 코드를 만들어,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > int main() { printf("threadid: %d", GetCurrentThreadId()); is_stack_available(1024 * 1024); getchar(); } </pre> <br /> 실행하면, VMMap으로 아래와 같이 확인할 수 있습니다.<br /> <br /> <img onclick='toggle_img(this)' class='imgView' alt='thread_stack_valid_1.png' src='/SysWebRes/bbs/thread_stack_valid_1.png' /><br /> <br /> 즉, 애써 Guard 페이지를 사용해가며 working set을 줄이려한 노력을 물거품으로 만들어 버리는 것입니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 그다음 소개하는 <a target='tab' href='https://devblogs.microsoft.com/oldnewthing/20200610-00/?p=103855'>part 2</a>의 코드는 Win32 API의 도움을 받아 Guard 페이지를 보호하면서 계산하는 방법을 소개하고 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > __declspec(noinline) bool is_stack_available(size_t amount) { ULONG_PTR low, high; GetCurrentThreadStackLimits(&low, &high); auto remaining = reinterpret_cast<ULONG_PTR>(&low) - low; if (remaining > high - low) { __fastfail(FAST_FAIL_INCORRECT_STACK); } return remaining >= amount; } </pre> <br /> 위의 함수는 스택에 할당된 로컬 변수 low의 주소를 기준으로 <a target='tab' href='https://www.sysnet.pe.kr/2/0/13174#GetCurrentThreadStackLimits'>GetCurrentThreadStackLimits</a> 내에 amount 만큼의 공간이 있는지 확인하고 있습니다. 물론, low 주소는 컴파일러가 별도의 스택 공간을 확보하거나, 아니면 <a target='tab' href='https://www.sysnet.pe.kr/2/0/10832#homing_space'>home space</a> 또는 심지어 <a target='tab' href='https://www.sysnet.pe.kr/2/0/13171'>red zone</a>을 사용하는 것도 가능하므로 정확한 계산이라고 볼 수는 없습니다.<br /> <br /> 또한, 스택이 최대로 자랐을 때 마지막 Guard 공간으로 남을 영역까지 모두 포함하고 있기 때문에 이에 대한 보정도 필요합니다. 재미있게도 이에 대한 크기를 알 수 있는 방법이 있는데,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > SetThreadStackGuarantee ; <a target='tab' href='https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-setthreadstackguarantee'>https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-setthreadstackguarantee</a> </pre> <br /> Windows XP/Server 2008부터 제공하던 것이니 GetCurrentThreadStackLimits를 쓸 수 있는 환경이면 문제될 것이 없습니다. 그래서 이에 대한 계산까지 C#으로 포팅해 보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > [DllImport("kernel32.dll")] static extern void GetCurrentThreadStackLimits(out ulong LowLimit, out ulong HightLimit); [DllImport("kernel32.dll")] [return: MarshalAs(UnmanagedType.Bool)] static extern bool SetThreadStackGuarantee(ref ulong StackSizeInBytes); [MethodImpl(MethodImplOptions.NoInlining)] static unsafe bool IsStackAvailable(ulong amount) { ulong low, high; nuint lowAddr = new nuint(&low); GetCurrentThreadStackLimits(out low, out high); var remaining = (ulong)lowAddr - low; if (remaining > high - low) { Environment.FailFast("FAST_FAIL_INCORRECT_STACK"); } ulong guarantee = 0; SetThreadStackGuarantee(ref guarantee); return remaining >= (amount + guarantee); } </pre> <br /> 그런대로 매끄럽게 작성됐습니다. 간단하게 테스트를 해볼까요? ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > static unsafe void Main(string[] args) { ulong max = 0; for (ulong i = 0x1000; i < (1024 * 1024 * 2); i++) { <span style='color: blue; font-weight: bold'>if (IsStackAvailable(i) == false)</span> { Console.WriteLine(i); break; } <span style='color: blue; font-weight: bold'>max = i;</span> } byte* buffer = <span style='color: blue; font-weight: bold'>stackalloc byte[(int)max];</span> } </pre> <br /> 실행해 보면, 아쉽게도 이런 식으로 예외가 발생합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 1550649 Stack overflow. at Program.Main(System.String[]) </pre> <br /> 아무래도 Managed 환경의 특성상 약간의 보정이 필요해 보이는데요, 간단하게 guarantee를 2배로 잡는 식으로 조정하고,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > static unsafe bool IsStackAvailable(ulong amount) { // ...[생략]... return remaining >= (amount + <span style='color: blue; font-weight: bold'>guarantee * 2</span>); } </pre> <br /> 다시 실행하면 이제는 "Stack overflow" 예외가 발생하지 않습니다. 하지만, stackalloc 하위에 다음과 같이 간단한 Console.WriteLine이라도 포함하게 되면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // ...[생략]... byte* buffer = stackalloc byte[(int)max]; <span style='color: blue; font-weight: bold'>Console.WriteLine(buffer[0]);</span> </pre> <br /> 이제는 다시 stack overflow 예외가 발생합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 1533817 Stack overflow. at System.Number.UInt32ToDecStr(UInt32) at System.IO.TextWriter.Write(Int32) at System.IO.TextWriter.WriteLine(Int32) at System.IO.TextWriter+SyncTextWriter.WriteLine(Int32) at System.Console.WriteLine(Int32) at Program.Main(System.String[]) </pre> <br /> 가용한 스택 공간까지 할당한 다음, 이후의 Console.WriteLine을 실행하는 중 callstack을 따라 할당되는 스택 공간을 감당하지 못한 것입니다. 따라서, 상황에 따라 스택에 대한 여유 공간 확인은 넉넉하게 해야 합니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 위의 코드를 현실적으로 사용할 만한 시나리오라면... 재귀 호출이 있는 경우 디버깅 용으로 넣어두는 것도 좋을 듯합니다. 예를 들어 이렇게 코드를 넣어두면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > static unsafe void Main(string[] args) { Console.WriteLine(CallRecursive(0)); } // 재귀 함수 중에서도 <a target='tab' href='https://www.sysnet.pe.kr/2/0/11205'>tail call</a> 최적화가 이뤄지면 스택 체크가 무의미 unsafe static int CallRecursive(int count) { <span style='color: blue; font-weight: bold'>if (IsStackAvailable(0x1000) == false) // 안전하게 4KB는 남도록 체크 { return count; }</span> Console.WriteLine(count); CallRecursive(count + 1); return count; } </pre> <br /> stack overflow까지 가기 전에 CallRecursive 재귀 호출을 안전하게 탈출할 수 있습니다. 물론, 0x1000의 체크와 IsStackAvailable 호출로 인한 간섭으로 실제 호출할 수 있는 재귀의 깊이보다 낮아질 수 있다는 점은 염두에 두어야 합니다.<br /> <br /> (첨부 파일은 이 글의 예제 코드를 포합합니다.)<br /> <br /> <hr style='width: 50%' /><br /> <br /> <a target='tab' href='https://devblogs.microsoft.com/oldnewthing/20200610-00/?p=103855'>part 2</a> 글을 보면 재미있는 설명이 하나 더 있습니다. C/C++의 경우 is_stack_available 함수가 인라인되는 것을 막아야 하는 사례로 coroutine을 이야기 합니다. 왜냐하면 coroutine의 경우 C/C++ 컴파일러가 로컬 변수의 사용 범위에 따라 stack이 아닌 heap에도 놓일 수 있도록 최적화하는 것이 가능하기 때문입니다. 이로 인해 "low" 로컬 변수도 컴파일러의 재량에 따라 heap에 놓이게 되면 GetCurrentThreadStackLimits 범위에 속하지 않아 결국 is_stack_available 함수는 오동작하게 됩니다.<br /> <br /> 마지막으로 아래의 글은 stack overflow가 발생한 사례 분석을 보여줍니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > The case of the stack overflow exception when the stack is nowhere near overflowing ; <a target='tab' href='https://devblogs.microsoft.com/oldnewthing/20220204-00/?p=106219'>https://devblogs.microsoft.com/oldnewthing/20220204-00/?p=106219</a> </pre> <br /> 위의 경우는 스택 메모리가 아직 남아 있는데도 불구하고 stack overflow가 발생할 수 있음을 보여줍니다. <a target='tab' href='https://learn.microsoft.com/en-us/windows/win32/procthread/job-objects'>Job</a>에 소속돼 제한적인 메모리를 할당받은 상태에서 Job에 속해 있는 다른 프로세스의 메모리 과다 사용으로 인해 reserve 상태의 스택 메모리를 commit할 수 없어 stack overflow가 발생한 것입니다.<br /> <br /> 아마도... 이 정도만 알아두시면 향후 stack overflow 예외가 발생했을 때 적절한 대응을 할 수 있을 것입니다. ^^<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
2048
(왼쪽의 숫자를 입력해야 합니다.)