성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] Working with Rust Libraries from C#...
[정성태] Detecting blocking calls using asyn...
[정성태] 아쉽게도, 커뮤니티는 아니고 개인 블로그입니다. ^^
[정성태] 질문이 잘 이해가 안 됩니다. 우선, 해당 소스코드에서 ILis...
[양승조
] var대신 dinamic으로 선언해서 해결은 했습니다. 맞는 해...
[양승조
] 또 막혔습니다. ㅠㅠ var list = props[i].Ge...
[양승조
] 아. 감사합니다. 어제는 안됐던것 같은데....정신을 차려야겠네...
[정성태] "props[i].GetValue(props[i])" 코드에서 ...
[정성태] 저렇게 조각 코드 말고, 실제로 재현이 되는 예제 프로젝트를 압...
[정성태] Modules 창(Ctrl+Shift+U)을 띄워서, 해당 Op...
글쓰기
제목
이름
암호
전자우편
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'>windbg - CoTaskMemFree/FreeCoTaskMem에서 발생한 덤프 분석 사례 - 두 번째 이야기</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;' > windbg - CoTaskMemFree/FreeCoTaskMem에서 발생한 덤프 분석 사례 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/12058'>http://www.sysnet.pe.kr/2/0/12058</a> </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;' > windbg - CoTaskMemFree/FreeCoTaskMem에서 발생한 덤프 분석 사례 ; <a target='tab' href='http://sysnet.pe.kr/2/0/12058'>http://sysnet.pe.kr/2/0/12058</a> windbg/Visual Studio - HeapFree x86의 동작 분석 ; <a target='tab' href='http://sysnet.pe.kr/2/0/12059'>http://sysnet.pe.kr/2/0/12059</a> windbg/Visual Studio - HeapFree x64의 동작 분석 ; <a target='tab' href='http://sysnet.pe.kr/2/0/12060'>http://sysnet.pe.kr/2/0/12060</a> </pre> <br /> 다시 한번 가보겠습니다. ^^ 우선, callstack의 Finalizer에서 예외가 발생했던(OraOps19.dll!00007ffcf0628a29로 시작했던) 덤프를 먼저 분석할 텐데 Visual Studio로 해당 덤프를 열어 "Debug with Mixed"로,<br /> <br /> <img alt='debug_mixed_in_vs_1.png' src='/SysWebRes/bbs/debug_mixed_in_vs_1.png' /><br /> <br /> 시작하면 곧바로 다음과 같이 crash 당시에 발생한 예외를 인식해 창을 띄워줍니다.<br /> <br /> <img alt='debug_mixed_in_vs_2.png' src='/SysWebRes/bbs/debug_mixed_in_vs_2.png' /><br /> <br /> <div style='BACKGROUND-COLOR: #ccffcc; padding: 10px 10px 5px 10px; MARGIN: 0px 10px 10px 10px; FONT-FAMILY: Malgun Gothic, Consolas, Verdana; COLOR: #005555'> Unhandled exception at 0x00007FFD05D882D3 (ntdll.dll) in dump4580.dmp: 0xC0000374: A heap has been corrupted (parameters: <span style='color: blue; font-weight: bold'>0x00007FFD05DDF6B0</span>). </div><br /> <br /> 오호~~~ "<a target='tab' href='http://sysnet.pe.kr/2/0/12060'>windbg/Visual Studio - HeapFree x64의 동작 분석</a>" 글에서 설명했던 바로 그 "parameters" 정보입니다. 따라서 해당 주소를 메모리 창(Ctrl + Alt + M, 1)에서 보면,<br /> <br /> <img alt='vs_heap_free_3.png' src='/SysWebRes/bbs/vs_heap_free_3.png' /><br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0x00007FFD05DDF6B0 000006e000000002 0x00007FFD05DDF6B8 0000000000000004 0x00007FFD05DDF6C0 0000014e8cba0000 // hHandle 0x00007FFD05DDF6C8 00000152e6608b40 // (pVoid - 0x10) 위치 0x00007FFD05DDF6D0 0000000000000000 0x00007FFD05DDF6D8 0000000000000000 0x00007FFD05DDF6E0 0000000000000000 0x00007FFD05DDF6E8 00000152e66045e0 0x00007FFD05DDF6F0 00000152e660c5f0 0x00007FFD05DDF6F8 0000000000000000 0x00007FFD05DDF700 0000000000000000 </pre> <br /> 감각적으로 ^^ HeapFree로 해제하려는 대상 Heap Handle과 메모리 위치를 알 수 있습니다. 사실 <a target='tab' href='http://www.sysnet.pe.kr/2/0/12060'>지난 글</a>에서는 9번째 슬롯에 있는 값이 "pVoid - 0x10" 위치였는데 이번에는 4번째로 바뀌었습니다. (이런 건 OS 버전 또는 Patch의 차이일 수 있으니 센스 있게 낚으시면 됩니다. ^^)<br /> <br /> 참고로, (<a target='tab' href='https://learn.microsoft.com/en-us/windows/win32/api/heapapi/nf-heapapi-heapcreate'>HeapCreate</a>로 생성된) hHandle의 경우 운영체제에서 메모리 할당 시 Page 크기에 정렬되므로 마지막 4바이트가 "0000"으로 끝나는 것을 보고 그나마 쉽게 알아볼 수 있습니다. 또는, 더 확실하게 hHandle을 확정하고 싶다면 역시 지난 글에서 보여준 코드에 따라,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 00007FFDF6EFFBCF 49 8B F8 mov rdi,r8 // rdi == r8 == pVoid1 00007FFDF6EFFBD2 8B F2 mov esi,edx // esi == edx == 0 00007FFDF6EFFBD4 48 8B D9 mov rbx,rcx // rbx == rcx == hHandle 00007FFDF6EFFBD7 4D 85 C0 test r8,r8 00007FFDF6EFFBDA 74 52 je RtlFreeHeap+6Eh (07FFDF6EFFC2Eh) 00007FFDF6EFFBDC 48 85 C9 test rcx,rcx 00007FFDF6EFFBDF 0F 84 29 50 07 00 je memset+11C4Eh (07FFDF6F74C0Eh) <span style='color: blue; font-weight: bold'>00007FFDF6EFFBE5 81 7B 10 EE DD EE DD cmp dword ptr [rbx+10h],0DDEEDDEEh // dword([rbx + 10h]) == 0xffeeffee</span> </pre> <br /> [hHandle + 0x10] 위치의 (<a target='tab' href='https://doxygen.reactos.org/d9/df3/struct__HEAP.html#ad108db757b67f5c166b43fd7651e444a'>SegmentSignature</a>라고 불리는) dword 값이 ffeeffee 또는 ddeeddee인 것을 보고 확인하면 됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > <span style='color: blue; font-weight: bold'>0x0000014E8CBA0000</span> 0000000000000000 0x0000014E8CBA0008 0100eba53f7de08a <span style='color: blue; font-weight: bold'>0x0000014E8CBA0010</span> 00000002<span style='color: blue; font-weight: bold'>ffeeffee</span> // hHandle + 0x10 위치 0x0000014E8CBA0018 0000014e8d650018 0x0000014E8CBA0020 0000014e8cba0120 </pre> <br /> 또한, (pVoid - 0x10) 위치 값을 더 신뢰하고 싶다면 역시 <a target='tab' href='http://www.sysnet.pe.kr/2/0/12059'>지난 글</a>에서 call stack의 프레임 문맥 중 레지스터에 저장된 정보가 있었으므로,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > [ntdll.dll!RtlpLogHeapFailure()] rbx == 0000014E8CBA0000 == hHandle rsi == rdi == 00000152E6608B40 == (pVoid - 0x10) [ntdll.dll!RtlFreeHeap()] r14 == 0000014E8CBA0000 == hHandle rsi == 00000152E6608B40 == (pVoid - 0x10) rdi == 00000152E6608B50 == pVoid </pre> <br /> 확률을 높이는데 참고하시면 됩니다. 끝입니다, 이제 다음과 같이 해당 주소로 메모리 창을 통해 살펴보면 헤더가 "8b00000000000000"으로 깨져 있는 것이 보이고, 이후 공백 문자로 시작하는 SQL 쿼리가 보입니다. 이와 함께 문자열을 좀 더 쉽게 식별하기 위해 "Watch" 창에 "(char *)...address..."를 입력하면 "Text Visualizer"를 통해 완전한 문자열을 구할 수 있습니다. (보는 바와 같이 SQL 쿼리가 저장되어 있습니다.)<br /> <br /> <img onclick='toggle_img(this)' class='imgView' alt='debug_mixed_in_vs_3.png' src='/SysWebRes/bbs/debug_mixed_in_vs_3.png' /><br /> <br /> 분석은 여기까지입니다. 도대체 누가 어디서 8b00000000000000 값을 CoTaskMemAlloc으로 할당받은 메모리의 헤더에 덮어썼는지는 (snapshot에 불과한 메모리 덤프로는) 알 수 없습니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 그다음 Marshal.FreeCoTaskMem callstack을 가진 두 번째 덤프도 한 번 볼까요? ^^ 역시 Visual Studio로 열면 다음과 같은 예외 메시지의 창이 뜹니다.<br /> <br /> <div style='BACKGROUND-COLOR: #ccffcc; padding: 10px 10px 5px 10px; MARGIN: 0px 10px 10px 10px; FONT-FAMILY: Malgun Gothic, Consolas, Verdana; COLOR: #005555'> Unhandled exception at 0x00007FFD05D882D3 (ntdll.dll) in dump496.dmp: 0xC0000374: A heap has been corrupted (parameters: <span style='color: blue; font-weight: bold'>0x00007FFD05DDF6B0</span>). occurred </div><br /> <br /> 이젠 뭐 익숙해졌으니 ^^ 0x00007FFD05DDF6B0 값을 보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > <span style='color: blue; font-weight: bold'>0x00007FFD05DDF6B0</span> 000006e000000002 0x00007FFD05DDF6B8 0000000000000004 <span style='color: blue; font-weight: bold'>0x00007FFD05DDF6C0 00000286f28f0000 // hHandle 0x00007FFD05DDF6C8 0000028b412d3be0 // pVoid - 0x10</span> 0x00007FFD05DDF6D0 0000000000000000 0x00007FFD05DDF6D8 0000000000000000 0x00007FFD05DDF6E0 0000000000000000 0x00007FFD05DDF6E8 0000028b412cc9d0 0x00007FFD05DDF6F0 0000028b412d49e0 </pre> <br /> 0000028b412d3be0 주솟값을 구했고 이 위치의 메모리 내용을 살펴 보니,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > <span style='color: blue; font-weight: bold'>0000028b412d3be0</span> 0000000000000000 0000028b412d3be8 <span style='color: blue; font-weight: bold'>8b00000000000000</span> == header 깨짐 0000028b412d3bf0 202020202020200a == pVoid 위치 0000028b412d3bf8 6c65732020202020 0000028b412d3c00 44492e2020202020 </pre> <br /> 오호... 놀랍군요. ^^ 헤더를 깨뜨린 바이트 패턴(8b00000000000000)도 동일하고, 심지어 해당 메모리에 담겼던 SQL 쿼리 문자열도 동일합니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 이제 문제를 좀 더 특정하기 위해, Oracle.DataAccess.Client의 ExecuteReader 메서드를 보겠습니다. 다행히, FreeCoTaskMem 메서드가 호출되는 곳은 하나뿐이어서 쉽게 문제의 코드를 찾아낼 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > try { bool flag3 = ptr != null && ptr->pNewCommandText != IntPtr.Zero; num4 = <span style='color: blue; font-weight: bold'>OpsSql.Prepare2</span>(this.m_opsConCtx, ref this.m_opsErrCtx, ref this.m_opsSqlCtx, ref this.m_opsDacCtx, ref this.m_pOpoSqlValCtx, flag3 ? null : <span style='color: blue; font-weight: bold'>this.m_pooledCmdText</span>, <span style='color: blue; font-weight: bold'>ref zero2</span>, ref ptr, num5); } catch (Exception ex) { // ...[생략]... } finally { this.m_executeScalar = false; if (zero2 != IntPtr.Zero) { try { <span style='color: blue; font-weight: bold'>Marshal.FreeCoTaskMem(zero2);</span> } // ...[생략]... } // ...[생략]... } </pre> <br /> 그러니까, 위의 코드를 정리해 보면 string 타입인 this.m_pooledCmdText 필드에 있는 SQL 쿼리 텍스트가 (Native 모듈인 OraOps11w.dll에서 export한 OpsSqlPrepare2 함수를 호출하는) OpsSql.Prepare2 내에서 CoTaskMemAlloc을 할당받고 네이티브 heap에 쿼리 문자열이 복사된 다음 그 메모리 위치를 zero2 변수에 반환해 주는 구조입니다.<br /> <br /> 일단, 여기까지의 내용을 보면... 분명히 Oracle 쪽의 ODAC에 문제가 있는 듯합니다. 왜냐하면, 할당받은 메모리가 저 사이에 동일한 패턴으로 다른 스레드에 의해 침범을 당한다거나... 하는 확률은 극히 낮다고 볼 수 있기 때문입니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 해당 고객사도 Oracle 쪽에 이슈를 제기한 상황이라고 하니, ^^ 혹시나 후기가 들려오면 업데이트하겠습니다.<br /><br /> (2019-12-20 업데이트: 이 문제의 원인은 <a target='_blank' href='/2/0/12086'>windbg - Marshal.FreeHGlobal에서 발생한 덤프 분석 사례</a> 글에서 설명합니다.)<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
3757
(왼쪽의 숫자를 입력해야 합니다.)