성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] VT sequences to "CONOUT$" vs. STD_O...
[정성태] NetCoreDbg is a managed code debugg...
[정성태] Evaluating tail call elimination in...
[정성태] What’s new in System.Text.Json in ....
[정성태] What's new in .NET 9: Cryptography ...
[정성태] 아... 제시해 주신 "https://akrzemi1.wordp...
[정성태] 다시 질문을 정리할 필요가 있을 것 같습니다. 제가 본문에...
[이승준] 완전히 잘못 짚었습니다. 댓글 지우고 싶네요. 검색을 해보...
[정성태] 우선 답글 감사합니다. ^^ 그런데, 사실 저 예제는 (g...
[이승준] 수정이 안되어서... byteArray는 BYTE* 타입입니다...
글쓰기
제목
이름
암호
전자우편
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# - 일부러 GC Heap을 깨뜨려 GC 수행 시 비정상 종료시키는 예제</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;' > C#에서 확인해 보는 관리 힙의 인스턴스 구조 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/1176'>http://www.sysnet.pe.kr/2/0/1176</a> </pre> <br /> 관리 힙에 있는 객체는 메모리 구조가 OBJECT Header와 Method Table 주소 및 각 타입의 필드로 이뤄집니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > int[] test = new int[1]; test[0] = 0xff; Console.WriteLine("int [] ================== "); fixed (int* p1 = &test[0]) { Console.WriteLine("OBJECT Header(SyncBlock Index): " + (*(p1 - 3)).ToString("x")); Console.WriteLine("MethodTable Address: " + (*(p1 - 2)).ToString("x")); Console.WriteLine("배열 요소 크기: " + (*(p1 - 1)).ToString("x")); Console.WriteLine("0번째 배열 값: " + (*(p1 - 0)).ToString("x")); } </pre> <br /> 그래서, 만약 관리 힙의 시작 주소를 알 수 있다면 그 이후로 Method Table 주소를 이용해 해당 타입의 필드 정보를 보고 객체가 소유한 메모리의 크기를 구해 다음 객체가 할당된 시작점으로 이동할 수 있습니다. 실제로 GC는 이런 식으로 GC Heap을 열람하면서 객체 정리를 해나갑니다.<br /> <br /> 그렇다면, 당연히 "Method Table 주소"를 구할 수 없다면 GC는 해당 객체의 크기를 알 수 없어 패닉(?)에 빠지게 될 것입니다. ^^; 이것을 다음과 같이 간단한 코드로 테스트할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > using System; class Program { static unsafe void Main(string[] args) { int[] test = new int[1]; test[0] = 0xff; Console.WriteLine("int [] ================== "); fixed (int* p1 = &test[0]) { Console.WriteLine("OBJECT Header(SyncBlock Index): " + (*(p1 - 3)).ToString("x")); Console.WriteLine("MethodTable Address: " + (*(p1 - 2)).ToString("x")); <span style='color: blue; font-weight: bold'>*(p1 - 2) = 0; // GC의 MethodTable 주소 정보를 제거</span> Console.WriteLine("배열 요소 크기: " + (*(p1 - 1)).ToString("x")); Console.WriteLine("0번째 배열 값: " + (*(p1 - 0)).ToString("x")); } <span style='color: blue; font-weight: bold'>GC.Collect();</span> // 여기서 비정상 종료 Console.WriteLine("GCed!"); // 이 코드는 절대 수행되지 않음 } } </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;' > Log Name: Application Source: .NET Runtime Date: 2019-11-15 오후 10:45:32 Event ID: 1023 Task Category: None Level: Error Keywords: Classic User: N/A Computer: TESTPC Description: Application: ConsoleApp1.exe Framework Version: v4.0.30319 Description: The process was terminated due to an internal error in the .NET Runtime at IP 709E21D1 (70900000) with exit code 80131506. </pre> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Log Name: Application Source: Application Error Date: 2019-11-15 오후 10:45:32 Event ID: 1000 Task Category: (100) Level: Error Keywords: Classic User: N/A Computer: TESTPC Description: Faulting application name: ConsoleApp1.exe, version: 1.0.0.0, time stamp: 0xf39545aa Faulting module name: clr.dll, version: 4.8.4042.0, time stamp: 0x5d7a9e00 Exception code: 0xc0000005 Fault offset: 0x000e21d1 Faulting process id: 0x5f1c Faulting application start time: 0x01d59b565e367a5c Faulting application path: c:\temp\ConsoleApp1\bin\Debug\ConsoleApp1.exe Faulting module path: C:\Windows\Microsoft.NET\Framework\v4.0.30319\clr.dll Report Id: b790601d-4a9d-470c-b8a9-144beabee7ab Faulting package full name: Faulting package-relative application ID: </pre> <br /> <pre style='height: 400px; margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Log Name: Application Source: Windows Error Reporting Date: 2019-11-15 오후 10:45:35 Event ID: 1001 Task Category: None Level: Information Keywords: Classic User: N/A Computer: TESTPC Description: Fault bucket 2275602419194088766, type 1 Event Name: APPCRASH Response: Not available Cab Id: 0 Problem signature: P1: ConsoleApp1.exe P2: 1.0.0.0 P3: f39545aa P4: clr.dll P5: 4.8.4042.0 P6: 5d7a9e00 P7: c0000005 P8: 000e21d1 P9: P10: Attached files: \\?\C:\ProgramData\Microsoft\Windows\WER\Temp\WER4A26.tmp.mdmp \\?\C:\ProgramData\Microsoft\Windows\WER\Temp\WER4A95.tmp.WERInternalMetadata.xml \\?\C:\ProgramData\Microsoft\Windows\WER\Temp\WER4AC5.tmp.xml \\?\C:\ProgramData\Microsoft\Windows\WER\Temp\WER4AC7.tmp.csv \\?\C:\ProgramData\Microsoft\Windows\WER\Temp\WER4AE7.tmp.txt These files may be available here: \\?\C:\ProgramData\Microsoft\Windows\WER\ReportArchive\AppCrash_ConsoleApp1.exe_205af63bf864821f555164beb5b9ebbca41c_870f1ae3_efc6993a-ca59-44d7-95e8-0749fcc35e9a Analysis symbol: Rechecking for solution: 0 Report Id: b790601d-4a9d-470c-b8a9-144beabee7ab Report Status: 268435456 Hashed bucket: f856d214730f94afbf949057f6154d3e Cab Guid: 0 </pre> <br /> 비록 이번에도 "Heap"이 깨지긴 했지만 말 그대로 .NET CLR의 관리 Heap이기 때문에 Native Heap이 깨졌을 때처럼 <a target='tab' href='http://www.sysnet.pe.kr/2/0/12050'>FaultTolerantHeap</a>과 같은 대우는 받지 못합니다.<br /> <br /> 어쨌든 이런 식으로 관리 힙이 깨져 <a target='tab' href='http://www.sysnet.pe.kr/2/0/12051'>비정상 종료하는 경우 메모리 덤프</a>를 받으면, 다음과 같은 콜 스택을 확인할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > clr!WKS::gc_heap::mark_object_simple+60 clr!WKS::GCHeap::Promote+a8 <span style='color: blue; font-weight: bold'>clr!GcEnumObject+37</span> clr!EECodeManager::EnumGcRefs+840 clr!GcStackCrawlCallBack+139 clr!Thread::StackWalkFramesEx+92 clr!Thread::StackWalkFrames+9d clr!standalone::ScanStackRoots+43 clr!GCToEEInterface::GcScanRoots+db clr!WKS::gc_heap::mark_phase+170 clr!WKS::gc_heap::gc1+ae clr!WKS::gc_heap::garbage_collect+367 clr!WKS::GCHeap::GarbageCollectGeneration+1bd clr!WKS::GCHeap::GarbageCollectTry+71 clr!WKS::GCHeap::GarbageCollect+ac clr!GCInterface::Collect+69 mscorlib_ni!System.GC.Collect()+31 <span style='color: blue; font-weight: bold'>mscorlib_ni!System.GC.Collect()+31 Program.Main(System.String[])+188</span> clr!CallDescrWorkerInternal+34 clr!CallDescrWorkerWithHandler+6b clr!MethodDescCallSite::CallTargetWorker+16a clr!RunMain+1b3 clr!Assembly::ExecuteMainMethod+f7 clr!SystemDomain::ExecuteMainMethod+5ef clr!ExecuteEXE+4c clr!_CorExeMainInternal+dc clr!_CorExeMain+4d mscoreei!_CorExeMain+d6 mscoree!ShellShim__CorExeMain+9e mscoree!_CorExeMain_Exported+8 kernel32!BaseThreadInitThunk+19 ntdll!__RtlUserThreadStart+2f ntdll!_RtlUserThreadStart+1b </pre> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // ServerGC의 경우 clr!SVR::gc_heap::plan_phase+12bb clr!SVR::gc_heap::gc1+c3 clr!SVR::gc_heap::garbage_collect+f0 clr!SVR::gc_heap::gc_thread_function+74 clr!SVR::gc_heap::gc_thread_stub+9a clr!<lambda_5d4b8dc0ce3eb05b864e38505727a1c1>::<lambda_invoker_cdecl>+51 kernel32!BaseThreadInitThunk+14 ntdll!RtlUserThreadStart+21 </pre> <br /> 참고로 이때의 예외 유형은 System.ExecutionEngineException입니다.<br /> <br /> 아무튼, 테스트를 통해 알 수 있겠지만 관리 힙의 데이터 주소를 native 측에 전달하거나, 또는 unsafe/fixed로 다룰 때는 그만큼 주의를 기울여야 합니다. 위의 사례와는 반대로 해당 데이터를 넘어서 쓰게 되면 다음 객체의 헤더를 덮어쓰게 될 것이고 그럴 때에도 GC는 헤어날 수 없는 충격에 빠지게 될 테니까요. ^^<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
6227
(왼쪽의 숫자를 입력해야 합니다.)