map 파일과 cod 파일을 사용해 분석하는 방법 을 익혔다고 해도 정작 Exception이 발생한 주소를 모른다면 소용없다.

그래서... Memory Fault 등이 터졌을 때 이것을 로그에 기록한 후 죽도록 구현한다면 디버깅에 매우 도움이 될 것이다.
Memory Fault 와 같은 Critical Exception을 핸들링하는 방법은 크게 두가지가 있다.
이런 방법들을 잘 이용하면 Memory Fault가 발생했을 때 죽지 않고 계속 실행하게 하는 것도 가능할 것이다.

1. SEH (Standard Exception Handling) :  __try ~ __except 혹은 __try ~ __finally 구문
__try는 C++의 try ~ catch 구문과는 다르다.
(SEH에 관한 자세한 내용은 http://serious-code.net/moin.cgi/SEH 를 참고.)
__try를 사용하면 Memory Fault 를 포함한 모든 Exception을 핸들링할 수 있다. 코드는 대략 다음과 같이 된다.

DWORD	MakeLog(LPEXCEPTION_POINTERS lpException)
{
	printf("Exception : 0x%08X\r\n", 
                      lpException->ExceptionRecord->ExceptionCode);
	printf("Exception Address : 0x%08p\r\n", 
                      lpException->ExceptionRecord->ExceptionAddress);

	return EXCEPTION_EXECUTE_HANDLER;
}


int _tmain(int argc, _TCHAR* argv[])
{
	__try {
		printf ("Before Exception\r\n");
		*(LPINT)NULL = 1;  // Exception
//		StartWork();   // 진짜 EntryPoint를 여기서 호출해줄 수도 있다.

		printf ("After Exception\r\n");
	} __except (MakeLog(GetExceptionInformation()))
	{
		//SetLastError(ERROR_...);
 		return FALSE // 실패!!
	}

	return TRUE; // 성공했음.
}

실행 결과는 다음과 같이 된다.

Before Exception
Exception : 0xC0000005
Exception Address : 0x00401047

로그고 뭐고 필요 없고 무조건 죽지만 않으면 된다면 다음과 같이..
int _tmain(int argc, _TCHAR* argv[])
{
	__try {
		printf ("Before Exception\r\n");
		*(LPINT)NULL = 1;  // Exception
		printf ("After Exception\r\n");
	} __except (EXCEPTION_EXECUTE_HANDLER)
	{
		//SetLastError(ERROR_...);
 		return FALSE // 실패!!
	}

	return TRUE; // 성공했음.
}

위의 __except 부분의 상수를
   -. EXCEPTION_EXECUTE_HANDLER 는 예외처리루틴을 실행하라는 뜻이다. 여기서 말하는 예외처리루틴이란
       __except 블럭 내의 코드를 말한다.
   -. EXCEPTION_CONTINUE_SEARCH 로 설정하면 try~catch 에서 예외를 던지는 것과 같은 효과가 된다.
      즉, 바깥 함수에 다시 __try ~ 구문이 기다리고 있지 않다면 프로그램은 예외를 내게 된다는 의미.
   -. EXCEPTION_CONTINUE_EXECUTION  로 설정하면 예외가 발생한 라인부터 다시 실행을 시도한다.
      만약 예외가 해결되지 않은 상황에서 다시 실행을 시도한다면?? 무한루프가 발생한다.

만약 Access Violation만 핸들링하고 싶다면 아래와 같이 하면 된다.
(※ EXCEPTION_EXECUTE_HANDLER의 Define값은 1이다)
int _tmain(int argc, _TCHAR* argv[])
{
	__try {
		printf ("Before Exception\r\n");
		*(LPINT)NULL = 1;  // Exception
		printf ("After Exception\r\n");
	} __except (GetExceptionCode()==EXCEPTION_ACCESS_VIOLATION)
	{
		//SetLastError(ERROR_...);
 		return FALSE // 실패!!
	}

	return TRUE; // 성공했음.
}


SEH를 사용하는 방법의 장점이라면... 
- 구조화된 예외처리를 수행하기 때문에 코드가 깔끔해지는다
- 원하는 함수에만 한정하여 적용할 수 있다.
단점이라면...
- 퍼포먼스가 무진장 떨어진다.
- 소멸자가 정의된 클래스의 인스턴스를 __try 블럭 안에서 생성하게 되면 컴파일 에러가 발생하며,
try ~ catch 구문과 함께 사용할 수 없고... 등등의 제한이 있다.



2. SetUnhandledExceptionFilter API를 사용하는 방법

SetUnhandledExceptionFilter 함수를 사용해 Unhandled Exception이 발생할 때 호출될 Exception Handler를 등록할 수 있다. 일단 Exception Handler가 등록되면 Exception이 발생했을 때 Callback된다. 

아래의 코드는 상기 샘플코드와 동일한 결과를 나타낸다.
실제로 사용해보면.. __try 구문보다 여러모로 훨씬 편하고 좋다.
단, 이 방법을 사용할 경우 로그를 기록하고 죽게 할수는 있지만, 예외를 핸들링해서 게속 동작하게 할 수는 없는 것 같다.
LONG CALLBACK UnhandledExceptionHandler(EXCEPTION_POINTERS *lpExceptionInfo)
{
	printf("Exception : 0x%08X\r\n", 
                 lpExceptionInfo->ExceptionRecord->ExceptionCode);
	printf("Exception Address : 0x%08p\r\n", 
                 lpExceptionInfo->ExceptionRecord->ExceptionAddress);

	return EXCEPTION_EXECUTE_HANDLER;
}


int _tmain(int argc, _TCHAR* argv[])
{
	SetUnhandledExceptionFilter(UnhandledExceptionHandler);

	printf ("Before Exception\r\n");
	*(LPINT)NULL = 1;
	printf ("After Exception\r\n");

	return FALSE;
}




Posted by kuaaan
,


사랑합니다. 편안히 잠드소서