728x90

보통 malloc을 호출하였다면 반환되는 주소가 NULL이 아닌지 확인해야 하고, 프로세스나 쓰레드, 그리고 동기화 오브젝트들을 생성할 때에도 해당 함수 호출 후에 오류가 발생했는지 확인하는 코드를 삽입해야한다.

 

프로그램 실행시 발생하는 문제점 대부분을 예외라고 인식하자.

처리 불가능한 문제가 발생하면 프로그램은 종료하게 되는데, 문제가 발생한다고 해서 프로그램이 종료된다면 이 또한 문제가 아닐 수 없다. 외부적인 요소에 의한 문제점이든 내부적인 요소에 의한 문제점이든 구조적 예외처리 기법 관점에서 대부분 해결 가능해야한다.

프로그램 실행시 예측 가능한 대부분의 문제점을 예외로 간주하고, 처리가능하도록 프로그램을 구현해야 한다.

 

하드웨어 예외와 소프트웨어 예외

하드웨어 예외 : 하드웨어에서 인식하고 알려주는 예외

ex) 정수를 0으로 나누는 연산은 하드웨어 예외를 발생시킨다.

사칙연산을 수행하는 주체는 CPU이다. 연산을 수행하는 과정에서 정수를 0으로 나누는 요청이 들어올 경우 문제가 있다는 신호를 운영체제에 전달할 것이다. 이를 통해 운영체제는 예외 상황이 발생했음을 알고, 구조적 예외 처리 메커니즘에 의해 예외상황이 처리되도록 일을 진행시킨다.

 

소프트웨어 예외 : 소프트웨어에서 감지하는 예외이다.

작성한 프로그램 뿐만아니라 운영체제도 포함한다. 소프트웨어 예외는 프로그래머가 직접 정의할 수 있는 예외이다.

 

종료 핸들러(Termination Handler)

SEH(Structured Exception Handling), 즉 구조적 예외처리 메커니즘은 기능적 특성에 따라 크게 두가지로 나뉜다.

1.종료 핸들러(Termination Handler)

2.예외 핸들러(Exception Handler)

 

종료 핸들러의 기본 구성과 동작 원리

종료핸들러에서 사용되는 키워드 : __try, __finally

 

__try는 예외 핸들러에서도 사용된다.

__try와 __finally는 다음과 같은 형태로 구성된다.

 

__try

{

     //code

}

__finally

{

  //종료 처리

}

__try만 올 수 없고, __finally만 올 수도 없다. 이 둘 사이에는 다른 어떠한 코드도 삽입될 수 없다.

 

위 소스코드는 __try블록을 실행하면 반드시 __finally블록을 실행하라는 의미를 가진 코드이다.

__try 내부에 return문이 존재해도 return이 실행되기에 앞서서 __finally를 실행시키는것이 컴파일러에 의해 보장되어 있다.

컴파일러는 return문에 의해 반환되는 값을 임시 변수(컴파일러가 만들어내는 변수)에 저장하고, __finally블록을 실행한다음, 값을 반환하도록 바이너리 코드를 구성한다.

 

ExitProcess, ExitThread, exit 함수에 의한 프로세스 또는 쓰레드의 강제 종료는 __finally블로그이 실행으로 이어지지 않는다.

 

종료 핸들러 활용

종료핸들러를 보면 문제의 발생 유무에 상관없이 반드시 실행되어야 하는 코드를 실행시킬 수 있다.

예를들어 파일의 개방과 이에따른 종료상황이다.

개방된 파일을 적절히 닫아주지 못한다면, 데이터의 일부가 손실될 수도 있기 때문이다.

동적할당도 같은 맥락에서 생각할 수 있다. 동적할당한 메모리를 해제하지 않고 함수를 빠져나가게 된다면 할당된 메모리는 유출된 상태로 남아있게 된다.

 

종료 핸들러의 경우 "무조건 실행"이라는 특성을 가진다.

 

예외 핸들러(Exception Handler)

예외 핸들러는 "예외 상황 발생시 선별적 실행"

 

예외 핸들러와 필터(Exception Handler & Filter)

예외 처리 핸들러 기본 구성

__try

{

    //예외 발생 경계

}

__except(예외 처리 방식)

{

   //예외 처리를 위한 코드

}

 

__try 블록은 예외 상황이 발생 가능한 영역을 묶는데 사용된다.

__try 블록에서 예외 상황이 발생하면 __except블록에서 이 상황을 처리하게 된다.

 

__except문의 "예외 처리 방식"부분을 가리켜 예외 필터(Exception Filter)라 한다.

예외 필터를 통해 예외처리 메커니즘의 동작 방식을 결정한다.

예외 필터값은 총 세가지 이다. 즉 예외처리 핸들러가 동작하는 방식이 세가지라는 것이다.

 

예외 필터를 EXCEPTION_EXECUTE_HANDLER 로 선언할 경우, __try 블록의 예외 발생 라인 이후를 건너 뛰게 된다.

 __try블록의 범위는 예외 발생 가능영역 뿐만 아니라, 예외처리 이후에 실행위치도 고려해서 결정해야만 한다.

 

__try블록내에서 호출하는 함수내에서 발생하는 예외도 __try블록 내에서 발생하는 예외로 인식한다.

 

예외의 종류와 예외를 구분하는 방법

예외 상황에 따라서 처리하는 방식이 달라져야 하기 때문에 발생한 예외의 종류를 구분할 수 있어야 한다.

어떠한 종류의 예외가 발생했는지를 확인하기 위해서 GetExceptionCode함수를 호출하면 된다.

DWORD GetExceptionCode(void)

 

예외 정보

EXCEPTION_ACCESS_VIOLATION과 EXCEPTION_INT_DIVIDE_BY_ZERO는 시스템에서 발생할 수 있는 예외로서 이미 약속된 예외의 종류에 해당한다.

EXCEPTION_ACCESS_VIOLATION : 메모리 참조 오류

EXCEPTION_INT_DIVIDE_BY_ZERO : 정수를 0으로 나누는 예외

EXCEPTION_STACK_OVERFLOW :  스택 메모리가 넘쳤을 때(부족할 때)발생하는 예외

 

GetExceptionCode 함수는 __except 블록 내에서나 예외 필터 표현식을 지정하는 위치에서만 호출이 가능하다는 특징이 있다.

 

EXCEPTION_CONTINUE_EXECUTION & EXCEPTION_CONTINUE_SEARCH 예외 필터 표현식

EXCEPTION_CONTINUE_EXECUTION의 표현식의 경우 __except 블록의 내부코드를 실행하지 않고 예외가 발생한 그 위치로 이동하여 실행을 이어나간다.

 

EXCEPTION_CONTINUE_SEARCH 란 다른곳에 있는 예외 핸들러를 통해서 예외를 처리하라는 것이다.

예외 핸들러를 찾을 때는 함수가 호출된 순서(스택이 쌓여있는 순서)를 바탕으로 예외핸들러를 찾게된다.

EXCEPTION_CONTINUE_SEARCH은 예외가 처리되어야 하는 위치를 별도로 지정하기 위한 용도로 사용된다.

 

소프트웨어 예외(Software Exception)의 발생

void RaiseException(

    DWORD dwExceptionCode,

    DWORD dwExceptionFalgs,

    DWORD nNumberOfArguments,

    const ULONG_PTR * lpArguments

);

1.dwExceptionCode : 발생시킬 예외의 형태를 지정한다.

2.dwExceptionFlags : 예외 발생 이후의 실행방식에 있어서 제한을 둘때 사용한다.

3.nNumberOfArguments : 추가정보의 개수를 지정한다.

4.lpArguments : 추가정보를 전달한다.

위 함수는 예외 발생을 알리기 위한 용도로 사용된다.

이 함수가 호출되면 SEH 메커니즘이 작동되면서, 예외처리가 전개된다.

 

GetExceptionCode 함수호출을 통해 얻게되는 예외코드를 통해서 부가적인 정보를 얻을 수 있다.

 

RaiseException의 첫번째 인자로 EXCEPTION_NONCONTINUEABLE이 올수있다.

EXCEPTION_NONCONTINUABLE은 예외가 발생하면 프로그램을 종료시키는 인자이다. EXCEPTION_CONTINUE_EXECUTION은 예외처리흐름을 막는 용도로 사용한다.

 

GetExceptionInformation

예외발생시 GetExceptionCode가 반환해 주는 정보보다 더 많은 정보를 얻기 원한다면, GetExceptionInformation 함수호출을 고려할 수 있다. 이 함수는 "예외 필터 표현식을 지정하는 부분"에서만 호출 가능하다.

 

LPEXCEPTION_POINTERS GetExceptionInformation(void);

GetExceptionInformation 함수가 호출되면, EXCEPTION_POINTERS 구조체 변수의 주소값이 반환된다.

 

EXCEPTION_POINTERS 구조체

typedef struct _EXCEPTION_POINTERS

{

    PEXCEPTION_RECORD ExceptionRecord;

    PCONTEXT ContextRecord;

 }EXCEPTION_POINTERS, *PEXCEPTION_POINTERS;

 

EXCEPTION_POINTERS 구조체는 EXCEPTION_RECORD구조체 포인터 PEXCEPTION_RECORD와 CONTEXT의 구조체 포인터 PCONTEXT로 구성되어 있음을 알 수 있다.

즉 예외발생후 GetExceptionInformation 함수가 호출되면 다음과 같은 구조로 구성된 예외 관련 정보를 얻게된다.

EXCEPTION_POINTERS 구조체 변수에는 두가지 정보가 채워진다. 하나는 예외 자체에 대한 정보가, 또 하나는 프로세서(CPU)의 레지스터 데이터를 비롯한 프로세서의 종속적인 정보가 채워진다.

728x90

+ Recent posts