2010. 8. 3. 12:57
다음의 내용은 Quad Dimensions에서 Debugging 교육 자료로 만든 내용입니다.
본부장
가져 가실때는 이 정보 남겨 주시는것이 예의 겠지요 ^^
1. Heap 영역 침범을 확인 하기
아래의 코드를 보자
아래 코드는 thread를 두 개 만들어서 한쪽에서는 계속 추가 , 한쪽에서는 삭제하는 코드이다.
SDList sd1, sd2; // 링크 리스트 관리자 클래스
// 링크드 리스트에 메모리 추가하는 쓰래드
void testThread1( void *dummy )
{
char * tmp;
while (TRUE)
{
tmp = new char[10];
sd1.AddHead(tmp);
printf("add memory %d \n",sd1.GetCount());
}
}
// 링크드 리스트에서 메모리 삭제하는 쓰래드
void testThread2( void *dummy )
{
int idx = 0;
LPLISTDATA lpLIST = NULL;
while (TRUE)
{
idx = rand() % sd1.GetCount();
lpLIST = sd1.Pos(idx);
sd1.Delete(lpLIST);
printf("delete memory %d \n",sd1.GetCount());
}
}
///////////////////////////////////////////////////////////////////////////////////////
// Explain : multi thread heap 손상 테스트
///////////////////////////////////////////////////////////////////////////////////////
void errTestHeapCorruption1()
{
char * tmp;
for (int i=0; i<100; i++)
{
tmp = new char[10];
sd1.AddHead(tmp);
}
_beginthread(testThread1,0,NULL);
_beginthread(testThread2,0,NULL);
}
정상적이라면 두개의 쓰래드에 Critical Section등으로 동기화 처리를 했어야 한다.
위의 경우 운이 좋으면 빨리 문제가 발생하지만 , 대부분의 경우 오랜 시간 뒤에 윈도우의 메모리 관리자에서 많은 부분이 파손된 뒤에 문제가 발생한다.
(추가된 샘플 소스의 경우는 Enter 키를 누르면 프로그램이 종료하면서 , 모든 메모리를 정리 할 때 오류가 발생하는 것을 볼 수 있다.)
일단 콜스택을 보자
0:000> kb
ChildEBP RetAddr Args to Child
0012f920 779377a8 01692c40 003d0000 01692c10 ntdll!RtlpBreakPointHeap+0x23
0012f954 77902e7d 0000007f 00000bd8 0012fa1c ntdll!RtlpCoalesceFreeBlocks+0x8f9
0012fa4c 77902d68 01692c10 01692c18 01692c10 ntdll!RtlpFreeHeap+0x1f4
0012fa6c 7797583e 003d0000 50000063 01692c18 ntdll!RtlFreeHeap+0x142
0012fab4 77937aca 003d0000 50000063 01692c18 ntdll!RtlDebugFreeHeap+0x1f9
0012fba8 77902d68 01692c10 01692c18 01692c18 ntdll!RtlpFreeHeap+0x5d
0012fbc8 7743f1ac 003d0000 00000000 01692c18 ntdll!RtlFreeHeap+0x142
0012fbdc 0042209a 003d0000 00000000 01692c18 kernel32!HeapFree+0x14
0012fc1c 004054dd 01692c18 112b0e80 0043b2f0 Timetest!free+0x6e
0012fed4 00403b3c 00000001 00446560 003d122c Timetest!SDList::Clear+0x2fd
0012fee8 0043b2fd 0012ff34 00426f22 0043b2f0 Timetest!SDList::~SDList+0x1c
… 이하 생략 …
소멸자에서 메모리 정리하면서 죽은 것을 알 수 있고 , 최종적으로 RtlpCoalesceFreeBlocks 함수에서 문제가 나온 것이 확인 가능하다. (RtlpBreakPointHeap 함수는 오류가 생겼을 때 디버그 정지를 시켜주는 역할 )
RtlpCoalesceFreeBlocks 함수는 heap corruption 상황에서 매우 빈번하게 볼 수 있는 함수이다. 정규 함수가 아니기 때문에MSDN help에서는 찾을 수 없다.
Google에서 확인해 보면 MSDN blog에 유사한 상황으로 case study 자료가 있다.
위의 내용은 중요한 자료이며 충분히 이해를 할 필요가 있는 내용이다.
RtlpCoalesceFreeBlocks() 함수는 인접한 메모리가 삭제(free) 되었을 때 메모리 관리를 위해서 단편화된 메모리 블록들을 합치는 역할을 한다. 이때 중복삭제 / 삭제된 이후에 재사용 / 메모리 블록 정보 손상등의 경우 위의 함수에서 에러가 발생하게 된다.
계속 debugging을 진행해 보자.
0012fc1c 004054dd 01692c18 112b0e80 0043b2f0 Timetest!free+0x6e
위 코드에서 각각 열의 의미는 다음과 같다.
0012fc1c = 함수의 ebp
004054dd = EIP
01692c18 = free 함수의 첫 번째 인자 , 이하 두번째/세번째 인자
free함수는 두번째/세번째 인자를 받지 않으므로 나머지는 무시해야 한다.
만약 모든 함수의 인자를 보고 싶으면 kP 명령어를 사용하면 된다.
0:000> kP
ChildEBP RetAddr
0012f920 779377a8 ntdll!RtlpBreakPointHeap+0x23
0012f954 77902e7d ntdll!RtlpCoalesceFreeBlocks+0x8f9
0012fa4c 77902d68 ntdll!RtlpFreeHeap+0x1f4
0012fa6c 7797583e ntdll!RtlFreeHeap+0x142
0012fab4 77937aca ntdll!RtlDebugFreeHeap+0x1f9
0012fba8 77902d68 ntdll!RtlpFreeHeap+0x5d
0012fbc8 7743f1ac ntdll!RtlFreeHeap+0x142
0012fbdc 0042209a kernel32!HeapFree+0x14
0012fc1c 004054dd Timetest!free(
void * pBlock = 0x01692c18)+0x6e
0012fed4 00403b3c Timetest!SDList::Clear(
char bLock = 1 '')+0x2fd
0012fee8 0043b2fd Timetest!SDList::~SDList(void)+0x1c
다른 함수들은 인자를 받지 않고, free 함수에서만 포인터를 받고 있는 것을 확인 할 수 있다.
해당 메모리의 정보를 보자.
0:000> !address 0x01692c18
ProcessParametrs 001d13f0 in range 001d0000 001d7000
Environment 001d0810 in range 001d0000 001d7000
01660000 : 01660000 - 00034000
Type 00020000 MEM_PRIVATE
Protect 00000004 PAGE_READWRITE
State 00001000 MEM_COMMIT
Usage RegionUsageHeap
Handle 003d0000
그럼 heap의 정보를 보자
0:000> !heap 0x01692c18
Index Address Name Debugging options enabled
아무것도 없다.
정상적으로 존재하는 heap 메모리라면 아래처럼 보인다.
0:000> !heap 001d0000
Index Address Name Debugging options enabled
1: 001d0000
Segment at 001d0000 to 002d0000 (00007000 bytes committed)
위 내용으로 봐서 , 존재 하지 않는 메모리를 지우려고 했던 것을 알 수 있다.
이 내용을 조금 더 분석해 보기 위해 !heap –x 명령어를 사용해 보자.
0:000> !heap -x 0x01692c18
List corrupted: (Blink->Flink = 013ff2d8) != (Block = 01692bf0)
HEAP 003d0000 (Seg 01660000) At 01692be8 Error: block list entry corrupted
List corrupted: (Blink->Flink = 003d00c4) != (Block = 01692c48)
HEAP 003d0000 (Seg 01660000) At 01692c40 Error: block list entry corrupted
Entry User Heap Segment Size PrevSize Unused Flags
-----------------------------------------------------------------------------
01692be8 01692bf0 003d0000 01660000 58 30 0 free
위 정보를 보면 block 정보가 손상된 것을 대략적으로 확인할 수 있다.
물론 위와 같이 손상된 메모리가 존재한다는 것을 확인했다고 해도 , 현재 상태에서 디버깅을 통해서 어떤 코드에 의해서 문제가 발생했는지는 매우 힘들다.
이럴 경우는 아래와 같은 방법으로 디버깅을 권장한다.
1) Heap 메모리의 손상이 발생된 것이 확인되면 , heap 생성/삭제 부분을 중심으로 디버깅 진행
u 크래시가 발생하면 Dr. Watson에서 full dump를 쓰게 셋팅을 하고 , 해당 dump 내용을 이용해 call stack 등을 보고 오류 발생한 부분을 추적한다.
u WinDbg를 real 환경에 셋팅을 해서 문제 발생할 때 까지 모니터링 하다가 , 디버깅이 시작되면 현재 상태에서call stack을 보거나 코드를 검토한다.
2) 메모리를 생성할 때 로그를 남겨서 , 손상된 메모리와 비교
메모리를 생성할 때 (new/malloc) 등의 경우 생성된 메모리의 어드레스를 모두 로그로 남겨 둔다.
그리고 위와 같은 오류가 발생했을 때 발생한 메모리의 위치와 로그의 메모리 위치를 비교해 어떤 메모리가 깨졌는지 검사를 한다.
그럼 이 내용을 기준으로 어떤 부분에서 문제가 발생했는지 유추할 수 있다.
이런 방법으로 디버그를 하려면 메모리 풀을 만들어 두고 모니터링 하는 것이 더 편리하다.
댓글 없음:
댓글 쓰기