강연소개 Exception Handler 를통한에러검출및수정 디버깅을즐겨하십니까..? 에러를만나면반갑습니까..? 전화로버그보고를받았나요..? 잡히지않는버그!!!! 따분한강의 졸아도좋습니다!!!!
강연자소개 테스터 온라인게임클라이언트개발 로컬라이즈및해외지원업무 디버깅, 최적화, 호환성향상에관심
강연대상 x86 환경에서 Windows 프로그래밍 디버깅 / 에러추적은 CPU/OS에따라다르다 소개할기술은 x86/windows 에서사용됨. C/C++, Assembler 에대한구조적인지식 Stack 이나 Register 등의접근을요구함 Listing File 을참고해야하는경우가있다.
디버깅의난해함 작은개발팀에서의프로그램출시 테스팅 /QA 팀이없거나기술적으로미숙함 부족한시간에따른충분하지못한테스팅 에러에대한부족한정보 유저들에게충실한에러보고를요구할수없다. 에러의존재유무조차알수없는경우도많다. 온라인서비스 자주출시되는실행파일로테스트부족 서비스중단에따른금전적손실
C/C++ 버그의대표적인예 포인터, 포인터, 포인터!!! 기술적인에러의 98% 는포인터문제다. 간혹일어나는 divide by zero 발생확률이적어서디버깅이더어렵다. Debug Code 에선에러가나지않을수있다. Release 로컴파일해서디버깅은가능하다. 방어적인코딩으로는해결되지않는다. 방어적인코딩은에러를감출뿐이다.
SHE 의소개 SEH - Structured Exception Handling Windows 에서제공해주는예외처리기능 User Mode 에서의 S/W, H/W 예외의처리 예외의종류 STATUS_ACCESS_VIOLATION EXCEPTION_INT_DIVIDE_BY_ZERO 기타에러는 GetExceptionCode() 함수참고
각종예외 Windows 98
각종예외 Windows XP
각종예외 라그나로크
SEH 의사용 - try ~ except void main() { int* p = 0x00000000; // pointer to NULL puts("hello"); try{ puts("in try"); try{ puts("in try"); *p = 13; // causes an access violation exception; } finally{ puts("in finally"); } } except(puts("in filter"), 1){ puts("in except"); } puts("world"); }
SHE 의사용 - SetUnhandledExceptionFilter LPTOP_LEVEL_EXCEPTION_FILTER SetUnhandledExceptionFilter( LPTOP_LEVEL_EXCEPTION_FILTER lptoplevelexceptionfilter ); 기본구조는 try ~ except 와같음 미설정시윈도우의오류화면출력 try ~ except 가준비되지않은코드에서자동적으로호출됨
UnhandledExceptionFilter LONG UnhandledExceptionFilter( STRUCT _EXCEPTION_POINTERS *ExceptionInfo ); typedef struct _EXCEPTION_POINTERS { PEXCEPTION_RECORD ExceptionRecord; PCONTEXT ContextRecord; } EXCEPTION_POINTERS, *PEXCEPTION_POINTERS; SetUnhandledExceptionFilter 의인자 일반적인 CallBack 함수와같은형태 ExceptionInfo 에서 CONTEXT 정보를얻을수있다.
CONTEXT 란..? Thread Context CPU 의각종레지스터정보 CPU 에따라다른정보가들어있을수있음 디버깅에서의 Thread Context 실행중인쓰레드의정보를알수있다. 메모리의정보와조합하여프로그램의실행상태를모두파악할수있다.
X86 의 CONTEXT 구조체 typedef struct _CONTEXT { DWORD ContextFlags; DWORD Dr0; DWORD Dr1; DWORD Dr2; DWORD Dr3; DWORD Dr6; DWORD Dr7; FLOATING_SAVE_AREA FloatSave; DWORD SegGs; DWORD SegFs; DWORD SegEs; DWORD SegDs; DWORD Edi; DWORD Esi; DWORD Ebx; DWORD Edx; DWORD Ecx; DWORD Eax; DWORD Ebp; DWORD Eip; DWORD SegCs; // MUST BE SANITIZED DWORD EFlags; // MUST BE SANITIZED DWORD Esp; DWORD SegSs; BYTE ExtendedRegisters[MAXIMUM_SUPPORTED_EXTENSI ON]; } CONTEXT;
예제프로그램 (1) UnahdledExceptionFilter 예제
ExceptionFilter 에서정보를얻자. 레지스터의값에서얻을수있는정보 EIP 를통한현재실행명령의주소 ESP/EBP를통한 Stack 의범위 / 내용 범용레지스터로부터현재실행상태 메모리에서얻을수있는정보 ExceptionFilter도같은프로세스이다. ESP/EBP의범위는항상유효하다. Static/Global 한정보도항상유효하다.
Debug Help Library 의간단한소개 Debug Help Library 의기능 Symbol 의접근및정보제공 MiniDump 의접근및정보제공 DbgHelp.dll 을통해제공 Windows 2000 이후에는 OS 기본제공 프로그래머가직접배포할수있다. Debugging Symbol 컴파일시함수명 / 변수명정보가기록 Debug 모드에선 EXE 에포함
SymFromAddr The SymFromAddr function retrieves symbol information for the specified address. BOOL SymFromAddr( HANDLE hprocess, DWORD64 Address, PDWORD64 Displacement, PSYMBOL_INFO Symbol );
SymFromAddr (2) 실행중인주소의 SYMBOL_INFO 를읽어옴 CONTEXT 의 EIP 에해당함 프로그램은항상같은주소에서실행된다. SYMBOL_INFO 에서클래스함수나현재실행중인함수를얻어올수있다.
SymGetLineFromAddr The SymGetLineFromAddr function locates the source line for the specified address. BOOL SymGetLineFromAddr( HANDLE hprocess, DWORD dwaddr, PDWORD pdwdisplacement, PIMAGEHLP_LINE Line );
SymGetLineFromAddr (2) 실행중인주소의 IMAGEHLP_LINE 을읽어옴 CONTEXT 의 EIP 에해당함 IMAGEHLP_LINE 에서에러가발생한소스코드의파일명, 행수를얻어올수있다.
StackWalk The StackWalk function provides a portable method for obtaining a stack trace. BOOL StackWalk( DWORD MachineType, HANDLE hprocess, HANDLE hthread, LPSTACKFRAME StackFrame, PVOID ContextRecord, PREAD_PROCESS_MEMORY_ROUTINE ReadMemoryRoutine, PFUNCTION_TABLE_ACCESS_ROUTINE FunctionTableAccessRoutine, PGET_MODULE_BASE_ROUTINE GetModuleBaseRoutine, PTRANSLATE_ADDRESS_ROUTINE TranslateAddress );
StackWalk (2) Stack 을뒤져서 Return 주소를찾아낸다. 계속해서찾으면 CallStack 이된다. CONTEXT 의 EIP 와 ESP/EBP 로찾는다. Symbol 없을경우 ESP/EBP를기본으로찾아간다. Debugging Application 서적을참고하자.
Release 에서 PDB 만들기 Visual C++ 의설정 Release 옵션에서기본설정으로 PDB 생성않음 Project Setting->Link->[Debug]->Debug Info 주의사항 PDB 는실행파일과같은디렉토리나 Symbol 디렉토리에존재해야한다. Visual C++ 6.0 에서는 Run 명령으로는 PDB 를정상적으로찾지못한다.
예제프로그램 (2) ExceptionFilter 를통한에러로그출력
PDB 의배포와문제점 PDB 의배포 용량이크다. 프로그램의각종세밀한정보가들어있다. PDB 가없을경우 Symbol이없으므로 CallStack의에러를내용을알수없다. 최적화된경우 PDB 없이 StackWalk가 CallStack 을정상적으로찾을수없다.
Frame-Pointer Frame-Pointer 란..? 함수마다 Frame 정보를 Stack 에남긴다. Frame 정보는 EBP/ESP 정보들이다. Frame-Pointer-Omission 된상태에서는 FPO_DATA 데이터가 Symbol 에저장된다. Frame-Pointer 의설정 최적화시 Frame-Pointer-Omission이된다. CL.EXE 컴파일러의 /Oy- 옵션으로강제설정
PDB 없이 Exception 데이터생성 실행파일설정 긱종디버깅정보를남긴다. 실행파일은 FP 를함수에서남기도록설정 Exception Filter 는 StackWalk 로 CallStack 의주소만을알아내서로그로남기게된다. Symbol Reader 제작 PDB 를뒤져서 CallStack 의정보를읽을수있는버전으로해독한다.
주의사항 Time Stamp, 실행파일이름 여러버전의실행파일의출시 TimeStamp 를찍는다. 유저의시스템은가능한믿지말자. 어느실행파일의에러인가..? 여러개의실행파일을출시하는경우실행파일이름이없으면어떤에러인지알수없다.
예제프로그램 (3) PDB 없이 Exception 데이터생성 PDB,EXE 파일로 CallStack 정보를읽어내기
Call Stack 으로알아보기힘든버그 한줄에서여러가지작업을하는경우 Line 을알아도에러를특정할수없다. 최적화시에는순서가바뀔수있다. 최적화가되어있을경우실행순서가바뀐다. PDB 에잘못된행수가적힐수있다. 포인터에러는구분하기힘들수있다. 포인터의포인터연산 포인터가한줄에여러개있다.
Listing File 의생성및사용 Listing File 이란..? C/C++ 의컴파일결과물을보여주는텍스트파일 Source Code/Assembler/Machine Code Listing File 의생성 Project Settings->C/C++->Listing Files Listing File 의용도 SymFromAddr의 Displacement로에러확인 에러추적을위해서 Assembler 지식이필요함
Memory Dump 의유용성 Memory Dump 란..? ESP 가가르키는 Stack 의일정영역을기록 프로그래머가미리지정한특정주소를기록 Memory Dump 의사용법 Stack 의내용은 Listing File 참조 Listing File 에어셈블러코드만으로디버깅이안될때사용한다.
예제프로그램 (4) Listing 파일에서에러찾기 메모리덤프참고하기
CallStack 이비정상적으로보이는경우 Template Symbol 정보가정상적으로남아있지않는경우 iterator 가 end 를넘어간경우 inline 된함수들의디버깅 inline 은호출함수에녹아들어간다. CallStack 은 inline 의위치만표시한다. NULL Function Pointer 의호출 StackWalk 가비정상적인작동
디버깅이매우힘든에러 다른 Thread 에서에러 DirectX 내부에서호출되는에러가검출된다. 멀티쓰레드의경우해당 Thread 정보만남는다. ExceptionFilter 에걸리지않는종료 Windows NT 계열에서발생 Handle을너무많이쓰거나지속적으로증가시발생하는경우가많음. 에러추적이매우어려움
비정상적인 Data, System 문제도고려한다 일어날수없는에러 0.000001% 의에러도있다. 프로그램에서절대발생할수없는가..? 일단지나가자!!! 데이터에는문제가없는가..? 데이터의의존하는코드의문제 각종유저데이터도확인한다.
에러는어떻게모을것인가..? 유저들의직접전달에의한접수 네트워크를통한간단한툴을제작 모두자동화시킨다.
질문 / 답변받기