레이블이 디버그 (debug)인 게시물을 표시합니다. 모든 게시물 표시
레이블이 디버그 (debug)인 게시물을 표시합니다. 모든 게시물 표시

2018년 3월 20일 화요일

아래 디버그관련 예제

2010. 8. 3. 14:32


아래 디버그 관련 글들에서 사용한 예제 코드 입니다.

다음 내용중에 필요에 따라서 comment를 풀어서 사용하면 됩니다.

void __stdcall ErrorTest()
{
    //vldLeakTest();
    //checkArgument(1,2,3,4,5);
    //errTestHandleError();
    //errTestMemViolation();
    // ***** stack error *****
    errTestMemViolation2();
    //errTestStackOverrun();
  
    // ***** heap error *****
    //errTestHeap1();
    //errTestHeap2();
    //errTestMemViolation3();
    //errTestMemDel();
    //errTestHeapCorruption1();
    //errTestDeadLock();
}

참고 Deadlock으로 freeze된 상황 찾기

2010. 8. 3. 12:58

다음의 내용은 Quad Dimensions에서 Debugging 교육 자료로 만든 내용입니다.
본부장 
가져 가실때는 이 정보 남겨 주시는것이 예의 겠지요 ^^




참고 Deadlock으로 freeze된 상황 찾기


첨부된 소스의 errTestDeadLock()함수를 실행 시키면 dead lock이 발생된다.
실행시키면 잠시 후 *BUSY* Debuggee is running… 이라고 표시되고 멈춘 상태를 볼 수 있다.

Ctrl-Break 를 눌러서 일시 정지를 시킨다.
명령어를 이용해 현재 작동중인 thread목록을 보면 아래와 같다.
0:003> ~
   0  Id: 2f4.e30 Suspend: 1 Teb: 7ffdf000 Unfrozen
   1  Id: 2f4.1374 Suspend: 1 Teb: 7ffde000 Unfrozen
   2  Id: 2f4.1070 Suspend: 1 Teb: 7ffdd000 Unfrozen
.  3  Id: 2f4.4e0 Suspend: 1 Teb: 7ffdc000 Unfrozen   ( . 표시된 것이 현재 active thread 이다. )

위와 같이 멈춘 상황에서는 각각 thread별로 call stack를 확인해 봐야 한다.
~번호 kb 명령어를 이용해 thread별로 확인해 본다또는 ~* kb 명령어로 모든 thread출력

0:003> ~0 kb
ChildEBP RetAddr  Args to Child             
… 중략 
0012ff40 00420457 00000001 01331a60 01331ad8 Timetest!main+0x1f
0012ff88 77441174 7ffd4000 0012ffd4 7790b3f5 Timetest!__tmainCRTStartup+0xfb
0012ff94 7790b3f5 7ffd4000 93c140e8 00000000 kernel32!BaseThreadInitThunk+0xe
0012ffd4 7790b3c8 004204ae 7ffd4000 00000000 ntdll!__RtlUserThreadStart+0x70
0012ffec 00000000 004204ae 7ffd4000 00000000 ntdll!_RtlUserThreadStart+0x1b

0:003> ~1 kb
ChildEBP RetAddr  Args to Child             
0132fdc0 778f5e6c 778dfc72 00000044 00000000 ntdll!KiFastSystemCallRet
0132fdc4 778dfc72 00000044 00000000 00000000 ntdll!NtWaitForSingleObject+0xc
0132fe28 778dfb56 00000000 00000000 013312c0 ntdll!RtlpWaitOnCriticalSection+0x13e
0132fe50 004043ec 00446560 cfe17652 00000000 ntdll!RtlEnterCriticalSection+0x150
0132ff38 0041a946 01333df0 01333df0 0132ff80 Timetest!SDList::Lock+0x6c
0132ff48 00420058 00000000 cfe176ea 00000000 Timetest!testThread3+0x36
0132ff80 004200f4 0132ff94 77441174 013312c0 Timetest!_callthreadstart+0x1b
0132ff88 77441174 013312c0 0132ffd4 7790b3f5 Timetest!_threadstart+0x76
0132ff94 7790b3f5 013312c0 92e140e8 00000000 kernel32!BaseThreadInitThunk+0xe
0132ffd4 7790b3c8 0042007e 013312c0 00000000 ntdll!__RtlUserThreadStart+0x70
0132ffec 00000000 0042007e 013312c0 00000000 ntdll!_RtlUserThreadStart+0x1b

0:003> ~2 kb
ChildEBP RetAddr  Args to Child             
0143fdbc 778f5e6c 778dfc72 00000048 00000000 ntdll!KiFastSystemCallRet
0143fdc0 778dfc72 00000048 00000000 00000000 ntdll!NtWaitForSingleObject+0xc
0143fe24 778dfb56 00000000 00000000 013314f0 ntdll!RtlpWaitOnCriticalSection+0x13e
0143fe4c 004043ec 00446578 cf90765e 00000000 ntdll!RtlEnterCriticalSection+0x150
0143ff34 0041a9c6 00000000 013317a0 00000001 Timetest!SDList::Lock+0x6c
0143ff48 00420058 00000000 cf9076ea 00000000 Timetest!testThread4+0x36
0143ff80 004200f4 0143ff94 77441174 013314f0 Timetest!_callthreadstart+0x1b
0143ff88 77441174 013314f0 0143ffd4 7790b3f5 Timetest!_threadstart+0x76
0143ff94 7790b3f5 013314f0 929040e8 00000000 kernel32!BaseThreadInitThunk+0xe
0143ffd4 7790b3c8 0042007e 013314f0 00000000 ntdll!__RtlUserThreadStart+0x70
0143ffec 00000000 0042007e 013314f0 00000000 ntdll!_RtlUserThreadStart+0x1b

0:003> ~3 kb
ChildEBP RetAddr  Args to Child             
0153ff58 7794d279 928040b4 00000000 00000000 ntdll!DbgBreakPoint
0153ff88 77441174 00000000 0153ffd4 7790b3f5 ntdll!DbgUiRemoteBreakin+0x3c
0153ff94 7790b3f5 00000000 928040e8 00000000 kernel32!BaseThreadInitThunk+0xe
0153ffd4 7790b3c8 7794d23d 00000000 00000000 ntdll!__RtlUserThreadStart+0x70
0153ffec 00000000 7794d23d 00000000 00000000 ntdll!_RtlUserThreadStart+0x1b

0번은 main thread이고 , 1 2번에서 RtlEnterCriticalSection()이 호출되는 것으로 보아 여기서 dealock이 걸린 것임을 유추 할 수 있다.

해당 함수들을 역어셈블 해보면 내용이 더 쉽게 눈에 들어 온다.



0:003> uf Timetest!testThread3
… 중략 
209 0041a932 b990654400      mov     ecx,offset Timetest!sd3 (00446590)
209 0041a937 e8449afeff      call       Timetest!SDList::Lock (00404380)
210 0041a93c b958664400      mov     ecx,offset Timetest!sd4 (00446658)
210 0041a941 e83a9afeff      call       Timetest!SDList::Lock (00404380)

0:003> uf Timetest!testThread4
… 중략 
229 0041a9b2 b958664400      mov     ecx,offset Timetest!sd4 (00446658)
229 0041a9b7 e8c499feff      call       Timetest!SDList::Lock (00404380)
230 0041a9bc b990654400      mov     ecx,offset Timetest!sd3 (00446590)
230 0041a9c1 e8ba99feff      call       Timetest!SDList::Lock (00404380)

내용을 보면 전형적인 deadlock임을 이해 할 수 있을 것이다.


제일 처음 부분에서 ~ 명령어를 사용했을 때 4개의 thread가 모두 suspend 된것으로 나오는데 , 이것은 dos console 모드에서 작업을 해서 그렇고 window application이라면 정지된 thread suspend로 나오기 때문에 조금 더 이해가 쉽다.

Heap 영역 침범을 확인 하기

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) 등의 경우 생성된 메모리의 어드레스를 모두 로그로 남겨 둔다.
그리고 위와 같은 오류가 발생했을 때 발생한 메모리의 위치와 로그의 메모리 위치를 비교해 어떤 메모리가 깨졌는지 검사를 한다.
그럼 이 내용을 기준으로 어떤 부분에서 문제가 발생했는지 유추할 수 있다.
이런 방법으로 디버그를 하려면 메모리 풀을 만들어 두고 모니터링 하는 것이 더 편리하다.

Heap 손상의 제일 기본적인 체크

2010. 8. 3. 12:57

다음의 내용은 Quad Dimensions에서 Debugging 교육 자료로 만든 내용입니다.
본부장 
가져 가실때는 이 정보 남겨 주시는것이 예의 겠지요 ^^

1.     Heap 손상의 제일 기본적인 체크
힙 손상은 찾기 제일 어려운 것들 중에 하나이다문제가 난 부분에서 에러가 나 바로 죽는 경우는 괜찮지만 , 많은 경우 손상된 heap을 유지한 상태로 프로그램이 작동되는 경우가 있기 때문이다.

아래 경우는 heap 오류로 보기는 곤란하지만 제일 기본적인 코드이기 때문에 만들어 보았다.

char * p = NULL;
*p = 10;
할당 되지 않은 공간에 값을 넣는 초보적 오류이다.


 
WinDbg에서 돌리면 에러가 발생하며 바로 정지한다.
0:000> kb
ChildEBP RetAddr  Args to Child             
0013ff6c 00416cab 0013ff80 00416cc8 0041fde6 Timetest!errTestHeap1+0xe
0013ff74 00416cc8 0041fde6 0013ffc0 00418b40 Timetest!ErrorTest+0x8
0013ff80 00418b40 00000001 0375afa0 0375cf40 Timetest!main+0x1b
0013ffc0 7c82f23b 00000000 00000000 7ffd8000 Timetest!mainCRTStartup+0xb4
0013fff0 00000000 00418a8c 00000000 78746341 kernel32!BaseProcessStart+0x23
스택 손상이 아니기 때문에 위처럼 확인 가능하다.
전과 동일하게 uf 명령어로 어디까지 진행했는지 확인한다.

0:000> uf Timetest!errTestHeap1+0xe
Timetest!errTestHeap1+0xe [G:\byun\test_code\100716_umdh_test\Timetest.cpp @ 98]:
98 00416bd3 c6000a          mov     byte ptr [eax],0Ah
99 00416bd6 8be5            mov     esp,ebp
99 00416bd8 5d              pop     ebp
99 00416bd9 c3              ret

0:000> uf Timetest!errTestHeap1
Timetest!errTestHeap1 [G:\byun\test_code\100716_umdh_test\Timetest.cpp @ 95]:
95 00416bc5 55              push    ebp
95 00416bc6 8bec            mov     ebp,esp
95 00416bc8 51              push    ecx
96 00416bc9 c745fc00000000  mov     dword ptr [ebp-4],0
98 00416bd0 8b45fc          mov     eax,dword ptr [ebp-4]
98 00416bd3 c6000a          mov     byte ptr [eax],0Ah
99 00416bd6 8be5            mov     esp,ebp
99 00416bd8 5d              pop     ebp
99 00416bd9 c3              ret

EIP : 00416bd0 에서 ebp-4 주소에 있는 값을 eax에 넣은 것을 볼 수 있다.
그 값이 뭔지 확인해 보자.
0:000> dd ebp-4
0013ff68  00000000 0013ff74 00416cab 0013ff80
00000000 값이 들어 있다뭔가 이상하다.

그리고 다음 EIP에서는 eax주소에 10을 넣으려고 하는 것을 알 수 있다.
그러나 eax 주소는 위에서 본 것처럼 잘못된 값이 들어 있다확인해 보자
0:000> dd eax
00000000  ???????? ???????? ???????? ????????
00000000 즉 할당 되지 않은 주소로 값을 넣으려고 했기 때문에 문제가 발생한 것을 확인할 수 있다.

위와 같은 heap 손상 계열의 에러에서는 d (dump) 명령어가 매우 중요하다.
해당 heap주소의 da, dd, dv 사용 습관을 꼭 가져야 한다.

2.     Debug mode Heap memory 확인 하기
이전 페이지의 메모리 블록 다이어그램에서 디버그 모드에서는 여러 가지 옵션값이 들어 있다고 했었다.
dump 명령어 사용법을 익히는 차원에서 해당 값을 검증해 보자.

소스 코드는 아래와 같다.
char * p = NULL;
p = new char[10];
__asm { int 3 } // windbg에서 보기위해서 여기까지 진행하고 멈추게 인터럽트 요청

역어셈블 해보자.
0:000> uf Timetest!errTestHeap2
Timetest!errTestHeap2 [G:\byun\test_code\100716_umdh_test\Timetest.cpp @ 102]:
102 00403720 55              push    ebp
102 00403721 8bec            mov     ebp,esp
102 00403723 83ec48          sub     esp,48h
… 중간 생략 
105 0040374f 8945fc          mov     dword ptr [ebp-4],eax
107 00403752 cc              int     3
… 이하 생략 

메모리의 값을 확인해 보자.
0:000> dd eax
03803ff0  cdcdcdcd cdcdcdcd fdfdcdcd c0c0fdfd
03804000  ???????? ???????? ???????? ????????
초기화 하지 않은 메모리가 생성되어 있다.
변수 p에 들어 있는 값도 확인해 보자.
0:000> dv p
              p = 0x03803ff0 "???"
값은 알아 보기 힘들지만 주소는 위의 값과 일치하는 것을 알 수 있다.

메모리 블록 다이어그램에서처럼 값이 있는지 확인하기 위해 그 전의 메모리 내용까지 덤프를 해보자.
0:000> dd eax-20
03803fd0  03800fd0 00000000 00000000 00000000
03803fe0  0000000a 00000001 00000037 fdfdfdfd
03803ff0  cdcdcdcd cdcdcdcd fdfdcdcd c0c0fdfd

실제 10바이트만큼 할당되어 있는 것이 확인 가능하다( intel x86에서는 상위 하위 워드가 반대로 저장되어 있다.)
 4바이트씩 위-아래에 메모리 가드가(0xfd) 되어 있고 , 프로그램상에서 0x37번째로 생성된 heap 메모리이며 , Normal(1) type의 메모리 이며 , 10(a)바이트로 할당된 것이 확인 가능하다.

메모리의 정보도 출력을 해보자.
0:000> !address 0x03803ff0
    03790000 : 03803000 - 00001000
        Type     00020000 MEM_PRIVATE
        Protect  00000004 PAGE_READWRITE
        State    00001000 MEM_COMMIT
        Usage    RegionUsagePageHeap
        Handle   03791000

Heap 메모리임을 확인 할 수 있고만약 stack이라면 RegionUsageStack 이라고 표시된다.