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

비밀번호

댓글 작성자
 




... 91  92  93  94  95  96  [97]  98  99  100  101  102  103  104  105  ...
NoWriterDateCnt.TitleFile(s)
11509정성태4/28/201819998.NET Framework: 741. windbg로 확인하는 객체의 GC 여부
11508정성태4/23/201821570개발 환경 구성: 373. MSBuild를 이용해 프로젝트 배포 후 결과물을 zip 파일로 압축하는 방법파일 다운로드1
11507정성태4/20/201821461개발 환경 구성: 372. MSBuild - 빌드 전/후, 배포 전/후 실행하고 싶은 Task 정의
11506정성태4/20/201825745.NET Framework: 740. C#에서 enum을 boxing 없이 int로 변환하기 - 두 번째 이야기 [7]파일 다운로드1
11505정성태4/19/201818555개발 환경 구성: 371. Azure Web App 확장 예제 - Simple WebSite Extension
11504정성태4/19/201820036오류 유형: 465. Azure Web App 확장 - Extplorer File manager 적용 시 오류
11503정성태4/19/201819876오류 유형: 464. PowerShell - Start-Service 명령 오류 (Service 'xxx' cannot be started)
11502정성태4/17/201821729개발 환경 구성: 370. Azure VM/App Services(Web Apps)에 Let's Encrypt 무료 인증서 적용 방법 [3]
11501정성태4/17/201818643개발 환경 구성: 369. New-AzureRmADServicePrincipal로 생성한 계정의 clientSecret, key 값을 구하는 방법파일 다운로드1
11500정성태4/17/201819582개발 환경 구성: 368. PowerShell로 접근하는 Azure의 Access control 보안과 Azure Active Directory의 계정 관리 서비스
11499정성태4/17/201818180개발 환경 구성: 367. Azure - New-AzureRmADServicePrincipal / New-AzureRmRoleAssignment 명령어
11498정성태4/17/201818242개발 환경 구성: 366. Azure Active Directory(Microsoft Enfra ID)의 사용자 유형 구분 - Guest/Member
11497정성태4/17/201816296개발 환경 구성: 365. Azure 리소스의 액세스 제어(Access control) 별로 사용자에게 권한을 할당하는 방법 [2]
11496정성태4/17/201816738개발 환경 구성: 364. Azure Portal에서 구독(Subscriptions) 메뉴가 보이지 않는 경우
11495정성태4/16/201819035개발 환경 구성: 363. Azure의 Access control 보안과 Azure Active Directory의 계정 관리 서비스
11494정성태4/16/201815523개발 환경 구성: 362. Azure Web Apps(App Services)에 사용자 DNS를 지정하는 방법
11493정성태4/16/201817309개발 환경 구성: 361. Azure Web App(App Service)의 HTTP/2 프로토콜 지원
11492정성태4/13/201815325개발 환경 구성: 360. Azure Active Directory의 사용자 도메인 지정 방법
11491정성태4/13/201818457개발 환경 구성: 359. Azure 가상 머신에 Web Application을 배포하는 방법
11490정성태4/12/201817489.NET Framework: 739. .NET Framework 4.7.1의 새 기능 - Configuration builders [1]파일 다운로드1
11489정성태4/12/201815154오류 유형: 463. 윈도우 백업 오류 - a Volume Shadow Copy Service operation failed.
11488정성태4/12/201818263오류 유형: 462. Unhandled Exception in Managed Code Snap-in - FX:{811FD892-5EB4-4E73-A147-F1E079E36C4E}
11487정성태4/12/201817343디버깅 기술: 115. windbg - 닷넷 메모리 덤프에서 정적(static) 필드 값을 조사하는 방법
11486정성태4/11/201816454오류 유형: 461. Error MSB4064 The "ComputeOutputOnly" parameter is not supported by the "VsTsc" task
11485정성태4/11/201823662.NET Framework: 738. C# - Console 프로그램이 Ctrl+C 종료 시점을 감지하는 방법파일 다운로드1
11484정성태4/11/201824751.NET Framework: 737. C# - async를 Task 타입이 아닌 사용자 정의 타입에 적용하는 방법파일 다운로드1
... 91  92  93  94  95  96  [97]  98  99  100  101  102  103  104  105  ...