Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
 
(연관된 글이 2개 있습니다.)
(시리즈 글이 6개 있습니다.)
디버깅 기술: 130. windbg - CoTaskMemFree/FreeCoTaskMem에서 발생한 덤프 분석 사례
; https://www.sysnet.pe.kr/2/0/12058

디버깅 기술: 131. windbg/Visual Studio - HeapFree x86의 동작 분석
; https://www.sysnet.pe.kr/2/0/12059

디버깅 기술: 132. windbg/Visual Studio - HeapFree x64의 동작 분석
; https://www.sysnet.pe.kr/2/0/12060

디버깅 기술: 133. windbg - CoTaskMemFree/FreeCoTaskMem에서 발생한 덤프 분석 사례 - 두 번째 이야기
; https://www.sysnet.pe.kr/2/0/12062

디버깅 기술: 138. windbg와 Win32 API로 알아보는 Windows Heap 정보 분석
; https://www.sysnet.pe.kr/2/0/12068

디버깅 기술: 144.  windbg - Marshal.FreeHGlobal에서 발생한 덤프 분석 사례
; https://www.sysnet.pe.kr/2/0/12086




windbg - CoTaskMemFree/FreeCoTaskMem에서 발생한 덤프 분석 사례 - 두 번째 이야기

지난번 덤프 분석을,

windbg - CoTaskMemFree/FreeCoTaskMem에서 발생한 덤프 분석 사례
; https://www.sysnet.pe.kr/2/0/12058

사전 준비도 되었으니,

windbg - CoTaskMemFree/FreeCoTaskMem에서 발생한 덤프 분석 사례
; http://sysnet.pe.kr/2/0/12058

windbg/Visual Studio - HeapFree x86의 동작 분석
; http://sysnet.pe.kr/2/0/12059

windbg/Visual Studio - HeapFree x64의 동작 분석
; http://sysnet.pe.kr/2/0/12060

다시 한번 가보겠습니다. ^^ 우선, callstack의 Finalizer에서 예외가 발생했던(OraOps19.dll!00007ffcf0628a29로 시작했던) 덤프를 먼저 분석할 텐데 Visual Studio로 해당 덤프를 열어 "Debug with Mixed"로,

debug_mixed_in_vs_1.png

시작하면 곧바로 다음과 같이 crash 당시에 발생한 예외를 인식해 창을 띄워줍니다.

debug_mixed_in_vs_2.png

Unhandled exception at 0x00007FFD05D882D3 (ntdll.dll) in dump4580.dmp: 0xC0000374: A heap has been corrupted (parameters: 0x00007FFD05DDF6B0).


오호~~~ "windbg/Visual Studio - HeapFree x64의 동작 분석" 글에서 설명했던 바로 그 "parameters" 정보입니다. 따라서 해당 주소를 메모리 창(Ctrl + Alt + M, 1)에서 보면,

vs_heap_free_3.png

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 

감각적으로 ^^ HeapFree로 해제하려는 대상 Heap Handle과 메모리 위치를 알 수 있습니다. 사실 지난 글에서는 9번째 슬롯에 있는 값이 "pVoid - 0x10" 위치였는데 이번에는 4번째로 바뀌었습니다. (이런 건 OS 버전 또는 Patch의 차이일 수 있으니 센스 있게 낚으시면 됩니다. ^^)

참고로, (HeapCreate로 생성된) hHandle의 경우 운영체제에서 메모리 할당 시 Page 크기에 정렬되므로 마지막 4바이트가 "0000"으로 끝나는 것을 보고 그나마 쉽게 알아볼 수 있습니다. 또는, 더 확실하게 hHandle을 확정하고 싶다면 역시 지난 글에서 보여준 코드에 따라,

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)  
00007FFDF6EFFBE5 81 7B 10 EE DD EE DD cmp         dword ptr [rbx+10h],0DDEEDDEEh // dword([rbx + 10h]) == 0xffeeffee

[hHandle + 0x10] 위치의 (SegmentSignature라고 불리는) dword 값이 ffeeffee 또는 ddeeddee인 것을 보고 확인하면 됩니다.

0x0000014E8CBA0000  0000000000000000 
0x0000014E8CBA0008  0100eba53f7de08a 
0x0000014E8CBA0010  00000002ffeeffee // hHandle + 0x10 위치
0x0000014E8CBA0018  0000014e8d650018 
0x0000014E8CBA0020  0000014e8cba0120

또한, (pVoid - 0x10) 위치 값을 더 신뢰하고 싶다면 역시 지난 글에서 call stack의 프레임 문맥 중 레지스터에 저장된 정보가 있었으므로,

[ntdll.dll!RtlpLogHeapFailure()]
rbx == 0000014E8CBA0000 == hHandle
rsi == rdi == 00000152E6608B40 == (pVoid - 0x10)

[ntdll.dll!RtlFreeHeap()]
r14 == 0000014E8CBA0000 == hHandle
rsi == 00000152E6608B40 == (pVoid - 0x10)
rdi == 00000152E6608B50 == pVoid

확률을 높이는데 참고하시면 됩니다. 끝입니다, 이제 다음과 같이 해당 주소로 메모리 창을 통해 살펴보면 헤더가 "8b00000000000000"으로 깨져 있는 것이 보이고, 이후 공백 문자로 시작하는 SQL 쿼리가 보입니다. 이와 함께 문자열을 좀 더 쉽게 식별하기 위해 "Watch" 창에 "(char *)...address..."를 입력하면 "Text Visualizer"를 통해 완전한 문자열을 구할 수 있습니다. (보는 바와 같이 SQL 쿼리가 저장되어 있습니다.)

debug_mixed_in_vs_3.png

분석은 여기까지입니다. 도대체 누가 어디서 8b00000000000000 값을 CoTaskMemAlloc으로 할당받은 메모리의 헤더에 덮어썼는지는 (snapshot에 불과한 메모리 덤프로는) 알 수 없습니다.




그다음 Marshal.FreeCoTaskMem callstack을 가진 두 번째 덤프도 한 번 볼까요? ^^ 역시 Visual Studio로 열면 다음과 같은 예외 메시지의 창이 뜹니다.

Unhandled exception at 0x00007FFD05D882D3 (ntdll.dll) in dump496.dmp: 0xC0000374: A heap has been corrupted (parameters: 0x00007FFD05DDF6B0). occurred


이젠 뭐 익숙해졌으니 ^^ 0x00007FFD05DDF6B0 값을 보면,

0x00007FFD05DDF6B0  000006e000000002 
0x00007FFD05DDF6B8  0000000000000004 
0x00007FFD05DDF6C0  00000286f28f0000 // hHandle
0x00007FFD05DDF6C8  0000028b412d3be0 // pVoid - 0x10
0x00007FFD05DDF6D0  0000000000000000 
0x00007FFD05DDF6D8  0000000000000000 
0x00007FFD05DDF6E0  0000000000000000 
0x00007FFD05DDF6E8  0000028b412cc9d0 
0x00007FFD05DDF6F0  0000028b412d49e0 

0000028b412d3be0 주솟값을 구했고 이 위치의 메모리 내용을 살펴 보니,

0000028b412d3be0 0000000000000000
0000028b412d3be8 8b00000000000000 == header 깨짐
0000028b412d3bf0 202020202020200a == pVoid 위치
0000028b412d3bf8 6c65732020202020
0000028b412d3c00 44492e2020202020

오호... 놀랍군요. ^^ 헤더를 깨뜨린 바이트 패턴(8b00000000000000)도 동일하고, 심지어 해당 메모리에 담겼던 SQL 쿼리 문자열도 동일합니다.




이제 문제를 좀 더 특정하기 위해, Oracle.DataAccess.Client의 ExecuteReader 메서드를 보겠습니다. 다행히, FreeCoTaskMem 메서드가 호출되는 곳은 하나뿐이어서 쉽게 문제의 코드를 찾아낼 수 있습니다.

try
{
    bool flag3 = ptr != null && ptr->pNewCommandText != IntPtr.Zero;
    num4 = OpsSql.Prepare2(this.m_opsConCtx, ref this.m_opsErrCtx, ref this.m_opsSqlCtx, 
        ref this.m_opsDacCtx, ref this.m_pOpoSqlValCtx, flag3 ? null : this.m_pooledCmdText, ref zero2, ref ptr, num5);
}
catch (Exception ex)
{
        // ...[생략]...
}
finally
{
    this.m_executeScalar = false;
    if (zero2 != IntPtr.Zero)
    {
        try
        {
            Marshal.FreeCoTaskMem(zero2);
        }
        // ...[생략]...
    }
    // ...[생략]...
}

그러니까, 위의 코드를 정리해 보면 string 타입인 this.m_pooledCmdText 필드에 있는 SQL 쿼리 텍스트가 (Native 모듈인 OraOps11w.dll에서 export한 OpsSqlPrepare2 함수를 호출하는) OpsSql.Prepare2 내에서 CoTaskMemAlloc을 할당받고 네이티브 heap에 쿼리 문자열이 복사된 다음 그 메모리 위치를 zero2 변수에 반환해 주는 구조입니다.

일단, 여기까지의 내용을 보면... 분명히 Oracle 쪽의 ODAC에 문제가 있는 듯합니다. 왜냐하면, 할당받은 메모리가 저 사이에 동일한 패턴으로 다른 스레드에 의해 침범을 당한다거나... 하는 확률은 극히 낮다고 볼 수 있기 때문입니다.




해당 고객사도 Oracle 쪽에 이슈를 제기한 상황이라고 하니, ^^ 혹시나 후기가 들려오면 업데이트하겠습니다.

(2019-12-20 업데이트: 이 문제의 원인은 windbg - Marshal.FreeHGlobal에서 발생한 덤프 분석 사례 글에서 설명합니다.)




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]






[최초 등록일: ]
[최종 수정일: 7/7/2023]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 




... 106  107  108  109  110  [111]  112  113  114  115  116  117  118  119  120  ...
NoWriterDateCnt.TitleFile(s)
11150정성태2/21/201719340.NET Framework: 645. Visual Studio Fakes 기능에서 Shim... 클래스가 생성되지 않는 경우 [5]
11149정성태2/21/201723032오류 유형: 378. A 64-bit test cannot run in a 32-bit process. Specify platform as X64 to force test run in X64 mode on X64 machine.
11148정성태2/20/201721966.NET Framework: 644. AppDomain에 대한 단위 테스트 시 알아야 할 사항
11147정성태2/19/201721204오류 유형: 377. Windows 10에서 Fake 어셈블리를 생성하는 경우 빌드 시 The type or namespace name '...' does not exist in the namespace 컴파일 오류 발생
11146정성태2/19/201719887오류 유형: 376. Error VSP1033: The file '...' does not contain a recognized executable image. [2]
11145정성태2/16/201721345.NET Framework: 643. 작업자 프로세스(w3wp.exe)가 재시작되는 시점을 알 수 있는 방법 - 두 번째 이야기 [4]파일 다운로드1
11144정성태2/6/201724720.NET Framework: 642. C# 개발자를 위한 Win32 DLL export 함수의 호출 규약 (부록 1) - CallingConvention.StdCall, CallingConvention.Cdecl에 상관없이 왜 호출이 잘 될까요?파일 다운로드1
11143정성태2/5/201722101.NET Framework: 641. [Out] 형식의 int * 인자를 가진 함수에 대한 P/Invoke 호출 방법파일 다운로드1
11142정성태2/5/201730116.NET Framework: 640. 닷넷 - 배열 크기의 한계 [2]파일 다운로드1
11141정성태1/31/201724398.NET Framework: 639. C# 개발자를 위한 Win32 DLL export 함수의 호출 규약 (4) - CLR JIT 컴파일러의 P/Invoke 호출 규약 [1]파일 다운로드1
11140정성태1/27/201720151.NET Framework: 638. RSAParameters와 RSA파일 다운로드1
11139정성태1/22/201722851.NET Framework: 637. C# 개발자를 위한 Win32 DLL export 함수의 호출 규약 (3) - x64 환경의 __fastcall과 Name mangling [1]파일 다운로드1
11138정성태1/20/201721125VS.NET IDE: 113. 프로젝트 생성 시부터 "Enable the Visual Studio hosting process" 옵션을 끄는 방법 - 두 번째 이야기 [3]
11137정성태1/20/201719829Windows: 135. AD에 참여한 컴퓨터로 RDP 연결 시 배경 화면을 못 바꾸는 정책
11136정성태1/20/201719016오류 유형: 375. Hyper-V 내에 구성한 Active Directory 환경의 시간 구성 방법 - 두 번째 이야기
11135정성태1/20/201720006Windows: 134. Windows Server 2016의 작업 표시줄에 있는 시계가 사라졌다면? [1]
11134정성태1/20/201727417.NET Framework: 636. System.Threading.Timer를 이용해 타이머 작업을 할 때 유의할 점 [5]파일 다운로드1
11133정성태1/20/201723531.NET Framework: 635. C# 개발자를 위한 Win32 DLL export 함수의 호출 규약 (2) - x86 환경의 __fastcall [1]파일 다운로드1
11132정성태1/19/201735036.NET Framework: 634. C# 개발자를 위한 Win32 DLL export 함수의 호출 규약 (1) - x86 환경에서의 __cdecl, __stdcall에 대한 Name mangling [1]파일 다운로드1
11131정성태1/13/201723975.NET Framework: 633. C# - IL 코드 분석을 위한 팁 [2]
11130정성태1/11/201724483.NET Framework: 632. x86 실행 환경에서 SECURITY_ATTRIBUTES 구조체를 CreateEvent에 전달할 때 예외 발생파일 다운로드1
11129정성태1/11/201728870.NET Framework: 631. async/await에 대한 "There Is No Thread" 글의 부가 설명 [9]파일 다운로드1
11128정성태1/9/201723293.NET Framework: 630. C# - Interlocked.CompareExchange 사용 예제 [3]파일 다운로드1
11127정성태1/8/201722833기타: 63. (개발자를 위한) Visual Studio의 "with MSDN" 라이선스 설명
11126정성태1/7/201727567기타: 62. Edge 웹 브라우저의 즐겨찾기(Favorites)를 편집/백업/복원하는 방법 [1]파일 다운로드1
11125정성태1/7/201724410개발 환경 구성: 310. IIS - appcmd.exe를 이용해 특정 페이지에 클라이언트 측 인증서를 제출하도록 설정하는 방법
... 106  107  108  109  110  [111]  112  113  114  115  116  117  118  119  120  ...