본문 바로가기

My Study/Programming&Theory

예외처리 메커니즘

1. 종료 핸들러
종료 핸들러는 __try__finally로 이루어져 있습니다.

__try{
        Ezbeat 루틴
}
__finally{
        Hacker 루틴
}
코드를 보시면 __try안으로 들어와서 Ezbeat루틴을 실행하게 되면 __try루틴을 빠져나올 때 항상 __finally루틴을 실행하게 됩니다. 위 코드는 너무 추상적 이므로 직접 소스를 짜서 보도록 하겠습니다.

두 수를 입력 받아서 나누는 프로그램 입니다.

위 소스코드에서는 2가지 실험 결과를 통해 이해를 하실 수 있습니다. 결과를 보시겠습니다.


첫번째 결과에서는 9와 3을 나눠서 Result가 3이었습니다. 
__try를 빠져나오면서 __finally에 있는 코드를 실행 한 것을 볼 수 있습니다.

두번째 결과를 보시면 5와 5를 나눠서 Result가 1이었습니다.
그러면 __try에 있는 if문안으로 들어오게 됩니다. 리턴을 하게 되면서 main함수가 종료가 되겠지요.
하지만 __finally에 있는 코드는 여전히 실행을 하게 됩니다.

위 소스코드와 결과만 봐도 이해를 하셨을 것입니다. 그러면 __try루틴에 들어오게 되면 반드시 __finally루틴이 실행되느냐..
그건 아닙니다. 모든과정에 예외가 있듯이 이것도 예외가 있습니다.

몇몇 종료 함수들을 사용하게 되면 __finally루틴으로 들어오지 않고 프로그램이 끝나게 됩니다.
( ExitProcess, ExitThread, exit .. )
소스코드와 결과를 보시겠습니다.

return부분을 ExitProcess함수로 바꾸었습니다.

결과를 보면 __finally루틴으로 들어오지 않고 종료가 된 것을 볼 수 있습니다.

결론입니다.
__try 루틴을 빠져나오는 경우 ( __finally 루틴 실행 함 )   : return, continue, break, goto, 예외발생
__try 루틴을 빠져나오는 경우 ( __finally 루틴 실행 안함) : ExitProcess, ExitThread, TerminateProcess, exit

상황에 맞게 적절히 종료 핸들러를 사용해 더욱 깔끔한 소스코드를 짜보세요 ^^

2. 예외 핸들러
예외 핸들러는 __try__except로 이루어져 있습니다.
__try{
      예외발생 루틴
}
__except(예외처리 방식){
      예외가 발생됬을 때 처리하는 루틴
}
__try 안에서 예외가 발생하면 예외처리 방식에 따라 해당 예외를 처리하게 됩니다.

예외처리 방식에 올 수 있는 경우의 수는 3가지 입니다.
EXCEPTION_EXECUTE_HANDLER : 예외가 발생하면 __try루틴의 나머지 부분을 실행하지 않고 예외처리 루틴으로 감
EXCEPTION_CONTINUE_EXECUTION : 예외가 발생한 지점으로 돌아가 다시 실행하도록 하는 방식
EXCEPTION_CONTINUE_SEARCH : 함수가 호출된 순서를 바탕으로 예외핸들러를 다시 찾아서 예외처리 해라

보통 많이 쓰이는 것은 위 2가지 이고 마지막은 잘 쓰지 않습니다.
먼저 각각의 경우를 소스코드를 짜서 보여드리도록 하겠습니다.
EXCEPTION_CONTINUE_SEARCH 경우는 잘 쓰지 않으므로 설명은 따로 하지 않겠습니다.

ㄱ. EXCEPTION_EXECUTE_HANDLER

위 소스코드와 크게 다른 것은 없습니다. try에서 예외를 발생시켰을 때와 안시켰을 때의 결과를 보도록 하겠습니다.

예외발생 안시켰을 때


예외발생 시켰을 때


예외를 발생시키면 __try에서 남은 코드를 실행 시키지 않고 바로 예외처리 루틴으로 가게 되는 것을 볼 수 있습니다.

ㄴ. EXCEPTION_CONTINUE_EXECUTION

소스코드가 약간 복잡합니다.

출력 결과를 본 후에 설명을 하도록 하겠습니다.

처음에 5와 0을 입력 했더니 예외가 발생해서 Func함수로 들어왔습니다. (GetExceptionCode는 뒤에서 설명하겠습니다.)
해당 예외가 정수를 0으로 나눴을 때 발생한 예외이므로 if문 안으로 들어오게 됩니다.
안에서 입력받는 수가 두개의 정수를 다시 입력 받는게 아니라 뒤에 들어온 수만 입력을 받고 있습니다.

여기서 궁금하신 분이 계실 것입니다. 두 수를 전부다 입력 받아도 되겠지..라고 생각하신 분들이 계실 것입니다.
제가 테스트를 해보았습니다.

Func함수 내부를 살짝 바꾸었습니다. 두 수를 입력 받도록 되어 있습니다. 출력 결과를 보도록 하겠습니다.

마지막 결과를 따르면 Result가 3이 나와야 할 탠데 결과는 5가 나왔습니다.
첫번째 값은 계속 넣어도 무용지물인지에 대해 알아본 결과

예외가 발생한 이후에 나눗셈을 할 때 첫번째 수는 처음 입력한 수를 특정 메모리에 넣어두고 
정상적인 나눗셈을 수행할 때 처음 입력했던 값을 EAX레지스터로 옮긴다음에 두번째 수와 나눗셈을 수행하게 됩니다.
( 메모리에 넣어둔 값을 EAX로 옮기는 과정은 커널영역에서 하는 것 같습니다. )

이 부분은 이해하기가 어려우므로 그냥 그런다고 넘어가시면 됩니다.
쫌더 쉽게 말해보면 두 수를 나눌 때 처음 입력 받은 수는 특정 메모리에 저장되어 있다가 
다시 나눗셈을 수행할 때 가져와서 두번째 수와 나눗셈을 수행한다는 것입니다.
아래그림을 보시면 어느정도 이해가 되실 것입니다.


이정도면 이해가 되셨을꺼 같으니 결론을 내보면 EXCEPTION_CONTINUE_EXECUTION 옵션은 예외가 발생한 지점으로 다시 가서 코드를 실행하라는 뜻이됩니다.

위에서 설명하겠다고 한 GetExceptionCode입니다.
GetExceptionCode은 함수가 아닌 메크로 입니다. 예외가 발생했을 때 어떠한 예외가 발생했는지 알려주는 메크로 입니다.
많은 경우의 수가 있는데
위 링크로 가시면 볼 수 있습니다.

이러한 예외처리 방법을 코드상에서 많이 볼 수 있는데 알고있으면 좋을것 같아서 한번 써보았습니다.
그리고 이러한 예외처리 방법을 사용한 안티리버싱 기법도 있습니다.