Transcript ebp
Debugging Application 황종하 웨어밸리, 기술 연구소 www.warevalley.com 디버깅 툴바 Build controls Set Default Project Configuration Debug controls The Debug Toolbar Restart Stop Debugging Step Out Variables Disassembly Step Into QuickWatch Memory Watch Call Stack Step Over Registers Step to Cursor Debug Window Controls 코딩하는 동안에 디버깅하기 Assertion - 프로그램의 어떤 지점에서 어떤 조건이 꼭 참이라고 단언하는 것 : ASSERT() : 조건이 0으로 평가되면 경고 메시지 표시 - Assertion은 함수나 매크로이며, 디버그 빌드에서만 실행 - Assertion은 코드에서 또 다른 문서 자료로 활용 - 함수 데이터에 대해서 작성한 사람이 어떤 것을 예상하고 있었는지를 알려 줌 - 변수나 프로그램의 상태를 변경하지 않음 - Assertion에서 데이터를 변경하면 디버그 빌드와 릴리즈 빌드에서 서로 다르게 동작하게 됨 Assertion 규칙 - 한번에 하나씩 확인 - 한번에 여러 개의 조건을 확인하면, 어떤 조건 때문에 오류가 발생 했는지 알 수 없음 ASSERT( (i>0) && (NULL != szItem) && ((iLen > 0) && (iLen < MAX_PATH)) && (FALSE == IsBadWritePtr(szItem, iLen)) ); ASSERT(i>0); ASSERT(NULL != szItem); ASSERT((iLen > 0) && (iLen < MAX_PATH)); ASSERT(FALSE == IsBadWritePtr(szItem, iLen)); Assertion 규칙 - 오류 조건을 완벽하게 확인 -포인터 확인 시 NULL만 확인하지 말고 완전하게 확인 ASSERT(FALSE == IsBadCodePtr(pfnCallBack)); Assertion의 helper 함수 // 메모리가 실행 될 수 있는지 확인 BOOL IsBadCodePtr( FARPROC lpfn // memory address ); // 명시된 수의 바이트에 대해서 메모리 포인터를 읽을 수 있는지 확인 BOOL IsBadReadPtr( CONST VOID *lp, // memory address UINT_PTR ucb // size of block ); // 스트링 포인터가 명시된 최대 수나 스트링의 NULL까지 읽을 수 있는지 확인 BOOL IsBadStringPtr( LPCTSTR lpsz, // string UINT_PTR ucchMax // maximum size of string ); // 메모리 포인터가 명시된 바이트 수만큼 기록할 수 있는지 확인 BOOL IsBadWritePtr( LPVOID lp, // memory address UINT_PTR ucb // size of memory block ); // HWND 매개변수가 사용 가능한 윈도우인지 확인 BOOL IsWindow(HWND hwnd); VERIFY 매크로 - 디버그 빌드에서는 VERIFY 매크로가 보통 Assertion과 동일하게 작동 : 조건이 0으로 평가되면, VERIFY 매크로는 경고 메시지 표시 - 릴리즈 빌드에서는 VERIFY 매크로는 소스 코드에서 그냥 남아 있음 : 경고 메시지 표지하지 않음 - 변수를 만드는 부담을 줄일 수 있음 : 반환 값 확인 용 : VERIFY(ResetEvent()) Trace 문 사용하기 - 본질적으로는 printf 스타일의 디버깅 - Trace문은 [Output] 윈도우의 [Debug] 탭에 결과 출력 - “함수 : Trace문” 형식으로 표시 (권장) // example for TRACE int i = 1; char sz[] = "one"; TRACE( "Integer = %d, String = %s\n", i, sz ); // Output: 'Integer = 1, String = one' Advanced Break Point Skip Count 1 Break Point 설정 2 BreakPoint Dialog 4 3 5 Skip Count Conditional Expression 1 Break Point 설정 2 주의 사항 - C Style의 연산 자만 이용 가능 - 함수 호출 지원 안함 - 매크로 사용할 수 없음 BreakPoint Dialog 4 5 3 Global Expression Break Point를 먼저 설정하지 않음 g_strText : 0x004168f8 값의 변화가 생기면 Break 가 걸림 Window Message [Messages]탭 선택 1 2 Window Procedure 메시시 설정 3 디버깅 시작 함수 이름 사용 디버깅하고자 하는 함수 이름을 아는 경우 : [Location] 탭의 “Break at”에 함수 이름 등록 1 2 함수 이름 사용 Advance Breakpoint 대화 상자 이용하여 등록하기 1 2 Call Stack에서 Breakpoint 걸기 Call Stack 윈도우에서 원하는 위치에 팝업 메뉴 혹은 F9을 이용해서 Breakpoint를 걸 수 있음 Remote Debugging Remote Debugging - Remote Machine에서 TCP/IP를 통해 통신하면서 프로그램을 디버깅 - Local Machine에 디버깅을 위한 소스가 존재하고 Remote Machine에서 디버깅할 프로그램 수행 - Window Activation Code를 디버깅해야 할 때 : WM_SETFOCUS 등 - 그림을 그리는 코드를 디버깅해야 할 때 - 하나의 운영체제에서 또 다른 운영체제로 디버깅할 때 : Local(Target) Machine : Windows 2000 Remote Machine : Windows 98 : Local Machine : Desktop Remote Machine : Notebook - 전체 Visual C++를 설치해서 Machine의 설정을 많이 바꾸는 것을 원하지 않을 때 : 시스템 DDL 버전을 변경하는 문제 버그 재현 어려움 예제 : Graphic Code 예제 프로그램 결과 모습 Local Machine 설정하기 1 [Build]Debug Remote Connection… 2 Network(TCP/I_P) 3 4 Remote Machine 주소 입력 Remote Machine에 있어야 할 파일들 공유 폴더 설정 관련 파일 복사 - MSVCMON.EXE TLN0T.DLL DM.DLL MSVCRT.DLL … Debugging하고자 하는 실행 파일 Remote Machine 설정하기 2 1 Target Machine IP MSVCMON.EXE 실행 3 Connection Remote Debugging 시작 1 Remote Debugging을 위한 실행 경로 및 파일 이름 설정 2 디버깅 시작 (F5) Watch 윈도우 Watch 윈도우 Formatting Symbol Watch 윈도우 Formatting Symbol의 예 Watch 윈도우 Memory Location Formatting Symbol Watch 윈도우 Memory Location Formatting Symbol Watch 윈도우 Watch 윈도우에서 함수 호출 Automatically Expanding Data - [Watch] 윈도우 등에서 자신의 타입에 대해서 CRect, CPoint 등과 같이 확장 가능 - InstallDir/Microsoft Visual Studio/Common/MSDev98/Bin/AUTOEXP.DAT CRect rect에 대해 [Watch] 윈도우에서 자동 확장되어 표시 Automatically Expanding Data 자동 확장이 설정 안된 상태 User Defined Type과 같이 자신이 정의한 타입에 대해서는 직접 확장을 시켜줘야 자신이 원하는 값을 볼 수 있음 Automatically Expanding Data Rules type = [text]<member[,format]> … - type : 타입 이름 text : 멤버의 표시할 이름 member : 멤버의 이름 format : watch formatting 심볼과 동일 [] : optional Automatically Expanding Data Samples CPoint =x=<x> y=<y> CRect =top=<top> bottom=<bottom> left=<left> right=<right> CSize =cx=<cx> cy=<cy> Set Next Statement - 디버깅 할 때만 [Source] 윈도우와 [Disassembly] 윈도우 에 표시 - 이 명령은 다음 수행할 위치를 선택하는 것으로서 EIP (Instruction pointer) 레지스터의 값을 변경시켜 줌 - 하나의 함수 내에서 프로그램을 수정하며 디버깅 시 유리 : 이 명령은 단지 EIP를 바꿔 줄 뿐이다.그러므로 : 조심해서 사용하지 않으면 프로그램 Crash 우려 - Release Build에서는 [Disassembly] 윈도우에서만 사용 Disassembly Disassembly Disassembly 1 Original Source 2 Prolog 3 Local Variable 영역 확보 스택 증가 방향 Stack ebp Local Variable 영역 (회색 영역) 60h = 96 byte 3 high ebp . . . 60h esp Low Prolog Stack 7 ebp ebp . . . 60h edi ebx esi 7 edi esp Prolog Local Variable영역 을 모두 0xCCCCCCCC로 채 움 Stack 10 ebp ebp 0xCCCCCCCC 0xCCCCCCCC 10 rep stos : repeat store string ecx : 18h eax : 0CCCCCCCCh edi . . . 60h 0xCCCCCCCC ebx esi edi esp 할당 문장 수행 Local Variable은 ebp에 서 값을 빼는 스타일 : ebp-14h, ebp-10h … 2 1 char szSrc[] = “This is a Source” 문장에 대한 실행 Local Variable 할당 ebp – 14h 0x12FF6C에 할당 Stack 모습 Stack 모습 1 앞에서 보았던 Stack 모습 2 논리적인 Stack 모습 ebp ebp-4 ebp-8 ebp-Ch ebp-10h ebp-14h ebp 00 0xCCCCCC 0012FF7C urce 0012FF78 a So 0012FF74 is 0012FF70 This 0012FF6C . . 0XCCCCCC 60h edi ebx esi edi esp 함수 호출 1 파라미터 Stack에 삽입 : 오른쪽 왼쪽 삽입 3 논리적인 Stack 모습 ebp ebp . . . 60h edi ebx esi edi 10 szDest szSrc 2 파리미터가 삽입된 후 Stack 모습 esp 0A ebp-20h ebp-14h 파라미터 함수 호출 1 함수 호출 전 모습 2 MemCPY 함수가 호출된 상태 반환 주소 : 0x004010C2 3 함수 호출 직후의 Stack 모습 : 반환 주소를 갖고 있음 파라미터 읽기 파라미터는 ebp에서 값 을 더하는 스타일 : ebp+8, ebp+0Ch … Stack 5 6 7 10 0A szDest ebp-20h szSrc ebp-14h 파라미터 ret addr 1 MemCPY함수의 inline assembly code ebp ebp . . . edi 40h ebx ebp+8 esi 2 edi esp Epilog 2 4번까지 수행된 Stack 모습 0A 4 ebp-20h 파라미터 ebp-14h ret addr 1 Epilog 코드 ebp ebp 40h ebx esi edi esp Epilog 2 8번까지 수행된 Stack 모습 0A ebp-20h 8 1 Epilog 코드 파라미터 ebp-14h ret addr ebp 40h ebx esi edi esp Epilog 2 9번까지 수행된 Stack 모습 0A ebp-20h ebp-14h 9 1 Epilog 코드 ret addr ebp 40h ebx esi edi 파라미터 esp 파라미터 영역 해제 2 10번까지 수행된 Stack 모습 0A ebp-20h 파라미터 10 1 함수 호출 이후의 코드 ebp-14h ret addr ebp 40h ebx esi edi esp Calling Convention Calling Convention Calling Convention __cdecl : C/C++의 Default Calling Convention Calling Convention __cdecl 함수 호출 Stack에 파라미터 삽입 오른쪽에서 왼쪽으로 삽입 함수 호출 1 Caller : Stack 해제 __cdecl 함수 종료 : Epilog Code 2 Callee : 파라미터를 해제하지 않음 Calling Convention __ stdcall : 거의 모든 시스템 함수에서 사용 Calling Convention __stdcall 함수 호출 Stack에 파라미터 삽입 오른쪽에서 왼쪽으로 삽입 함수 호출 1 __stdcall 함수 종료 : Epilog Code Caller : 파라미터 해제 코드 없음 2 Callee : 파라미터를 해제 Calling Convention __fastcall : Intel 계열의 CPU에서만 적용됨 Calling Convention Stack에 파라미터 삽입 오른쪽에서 왼쪽으로 삽입 __fastcall 함수 호출 레지스터에 삽입 함수 호출 1 __fastcall 함수 종료 : Epilog Code Caller : 파라미터 해제 코드 없음 2 Callee : 파라미터를 해제 Calling Convention naked : VxD에서 주로 사용 Calling Convention naked 함수 호출 Stack에 파라미터 삽입 오른쪽에서 왼쪽으로 삽입 함수 호출 1 Stack 해제 naked 함수 종료 Prolog 코드가 존재하지 않음 2 파라미터를 해제하지 않음 크래시된 주소를 이용하여 소스와 라인 정보 찾기 1. 2. 3. MAP 파일 생성 MAP 파일 내용 크래시 주소에서 소스와 라인 정보 찾기 MAP 파일 생성 MAP 파일은 프로그램 전역 심볼과 소스 파일 라인 번호 정보에 대해서 텍스트로 나타낼 수 있는 유일한 방법 1. Project Setting -> [Link] tab Path/MapFileName /MAPINFO:EXPORTS /MAPINFO:LINES MAP 파일 내용 모듈 이름 링크 시간 로드 위치 Public 함수 정보 MAP 파일 내용 라인번호 해당 라인의 코드섹션으로부터의 오프셋 소스 파일과 라인번호 찾기 공식 Crash Address – Preferred Load Address – 0x1000 Crash Address : 0x00401042 Preferred Load Address : 0x00400000 0x00401042 – 0x00400000 – 0x1000 = 0x42 소스 파일과 라인번호 찾기 2 1 문제 소스 3 MAP 파일 크래시 발생 Release Version에서 라인번호 추가하기 Release Version에서 라인번호 추가하기