시스템 프로그래밍 17장 - 구조적 예외처리(SEH) 기법

김주현·2021년 10월 21일
0

시스템 프로그래밍

목록 보기
17/21

01 . SEH

-예외처리의 필요성

프로그램을 실제흐름과 흐름에 대한 예외처리 영역으로 나누고
프로그램이 동작하는 주요 흐름을 파악하고싶다면 "프로그램의 실제 흐름"을
예외처리 부분이 궁금하면 "흐름에 대한 예외처리 영역"을 보면된다. "프로그램의 실제 흐름"에 해당하는 키워드가 try이고 "위 흐름에 대한 예외처리 영역"에 해당하는 키워드가 except이다.

-예외와 에러의 차이점

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

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

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

구조적 예외처리에서 말하는 예외는 크게 두 가지로 나뉜다. 하나는 하드웨어 예외이고 다른 하나는 소프트웨어 예외 이다.

하드웨어 에외란 하드웨어에서 인식하고 알려주는 예외를 의미한다. 예외의 근본적 원인은 응용 프로그램에 있겠지만, 이를 감지하고 예외사항이 발생했음을 알리는 주체가 하드웨어라는 뜻이다. ex) 정수를 0으로 나눌경우 cpu가 문제가있다는 신호를 운영체제에 전달. 그러면 운영체제는 예외사항이 발생했음을 알고 , 구조적 예외처리 메커니즘에 의해 예외사항이 처리되도록 일을 진행 시킨다.

반면에 소프트웨어 예외는 소프트웨어에서 감지하는 예외를 의미한다. 여기서 말하는 소프트웨어는 여러분이 작성한 프로그램뿐만 아니라 운영체제를 포함한다.

소프트웨어 예외 라는 것은 프로그래머가 직접 정의할 수 있는 예외 이다.

02 종료 핸들러

SEH 즉 구조적 예외처리 메커니즘은 기능적 특성에 따라 크게 두 가지로 나뉜다. 하나는 종료 핸들러이고 나머지 하나는 예외 핸들러이다.

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

종료 핸들러에서 사용되는 키워드 두 가지는 다음과 같다.

try, finally
try는 종료와 예외 에서 그 역할에 약간의 차이를 보인다.

try와 finally는 다음과 같은 형태로 구성된다. 즉 컴파일러는 이 둘을 하나의 문장으로 인식한다.

"try 블록을 한 줄이라도 실행하게 되면, 반드시 finally 블록을 실행해라!"

return이 실행되기에 앞서서 __finally 블록이 실행되는것인가?

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

"return, continue,break,goto,"예외"은 try블록을 빠져나오게 한다.

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

  • 예외 핸들러

종료 핸들러는 " 무조건 실행 "이라는 특징을 지닌다. 반면에 예외 핸들러는 "예외상황 발생 시 선별적 실행"이라는 특징을 지닌다. 예외 핸들러는 일종의 약속이다. 때문에 논리적으로 바라보면 오히려 어렵게 느껴질 수 있다. 그냥 어떻게 약속해 놓았는지 파악해야한다.

-예외 핸들러와 필터

try 블록은 예외상황이 발생 가능한 영역을 묶는데 사용된다. 만약에 try 블록에서 예외 상황이 발생하면 이어서 등장하는 except 블록에서 이 상황을 처리하게 된다. 물론 예외 상황을 처리하는 적절한 코드는 상황에 맞게 개발자가 직접 구현해야 한다.

예외감지와 실행을 옮기는것은 컴파일러가 코드를 구성하고 실행과정에 Windows가 이를돕는다.

except문을 보면 "예외처리 방식" 이라고 쓰여진 부분이 있는데, 이 부분을 가리켜 예외필터라 한다, except블록을 전체적으로 보면 함수처럼 보이고, 예외필터는 인자가 전달되는 부분처럼 보인다. 그러나 결코 인자전달이 아닌 d예외처리 메커니즘을 어떻게 동작시킬지 그 방식을 결정하기위한 영역이다.
3가지의 예외처리 방식의 존재한다.

예외필터를 EXCEPTION_EXECUTE_HANDLER로 선언한 경우, try 블록의 나머지 부분을 건너 뛰게 된다.

여기서 잠깐 하드웨어로부터 감지되는 예외상황이라 할지라도, 감지된 예외를 인식하고 최종적으로 예외상황을 알려서 예외처리 메커니즘을 동작시키는 것은 Windows이기에 앞으로도 예외는 Windows에 의해서 발생한다고 설명하겠다.
try 블록의 범위는 단순히 예외 발생 가능 영역만으 고려해서 결정하지 않는다. 예외 처리 이후에 실행위치도 고려해서 결정해야한다.

-처리되지 않은 예외의 이동

함수내에서 예외가 발생할경우 스택 프레임의 상단부터 예외처리 문구를 탐색하고 main에도 존재하지 않는다면 프로그램은 종료됨

-핸들러의 중복

예외 핸들러는 중복이 가능하다. 뿐만 아니라 종료 핸들러와도 중복이 가능하다.

-정의되어 있는 예외의 종류와 예외를 구분하는 방법

예외상황에 따라서 처리하는 방식이 달라져야 하기 때문에 발생한 예외의 종류를 구분할 수 있어야한다. 예외가 발생했을 때 어떠한 종류의 예외가 발생했는지를 확인하기 위해서 GetExceptionCode함수를 호출하면 된다.

DWORD GetExceptionCode(void)

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

-EXCEPTION_CONTINUE_EXECUTION & EXCEPTION_CONTINUE_SEARCH

EXCEPTION_CONTINUE_EXECUTION- except블록의 내부를 실행하지 않고 예외가 발생한 그 위치로 다시 이동하여 실행을 이어나감

EXCEPTION_CONTINUE_SEARCH - 다른곳에서 예외를 처리하라

04 . 소프트웨어 기반의 개발자 정의 예외

-소프트웨어 예외의 발생

다음 함수는 소프트웨어 예외를 발생시키는 역할을 하는데, 이 함수를 통해서 예외의 종류를 늘릴 수 있다.

void RaiseException (
DWORD dwExceptionCode // 발생시킬 예외의 형태를 지정한다.
DWORD dwExceptionFlags // 예외발생 이후의 실행방식에 있어서 제한을 둘 때 사용한다.
DWORD nNumberOfArguments // 추가정보의 개수를 지정한다
const ULONG_PTR* lpArguments // 추가정보를 전달한다.
);

첫번째 인자는 예외를 지정한다 기본적인 방식이 이미 정해져 있다.

인덱스 기준으로 31,30번째 비트는 에외의 심각도 수준을 나타낸다

00 : 성공
01 : 예외의 알림
10 : 예외에 대한 경고
11 : 강도 높은 오류 상황

29 번째 비트는 예외를 정의한 주체에 대한 정보를 담는다 ms가 정의한 예외는 0 , 개발자가 정의한 예외는 1을 넣기로 약속했다

28은 시스템에 의해서 예약되어있다 0으로 초기화한 상태로 내ㅓ려둔다.

16 ~ 27 까지는 예외발생 환경 정보를 담게된다.

0 ~ 15 까지는 예외의 종류를 구분하는 용도로 사용이된다.

  • GetExceptionInformation

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

이함수가 호출되면 EXCEPTION_POINTERS 구조체 변수의 주소값이 반환된다. 이 구조체 변수에는 예외에 관련된 정보가 채워지는데, 어떠한 정보로 채워지는지를 알 필요가 있다. EXCEPTION_POINTERS 구조체는 다음과 같이 선언되어 있다.

EXCEPTON_RECORD는 프로세서 비종속적 예외 관련 데이터
CONTEXT는 프로세서 종속적 데이터 이다.

예외 자체에 대한 정보 EXCEPTION_RECORD

RaiseException 함수의 세 번째 네 번째 전달인자는 main 함수의 argc,argv와 같은 역할을한다. 특정 예외상황 발생 시에 예외와 관련된 정보를 담아서 전다랗고싶다면 이 인자들을 활요하면 된다.

이것만은 알고 갑시다.

  1. SEH가 가져다 주는 이점

프로그램의 흐름에 관련된 코드와 예외를 처리하는 코드를 분리시켜 준다는 것은 큰 장점이다. 뿐만 아니라 프로그램을 디자인 하는데도 많은 도움을 준다. SEH의 특성을 잘이용하면 프로그램의 흐름을 간결히 할 수 있고, 그만큼 코드의 양도 줄일 수 있다.

  1. 종료 핸들러의 동작 원리와 적용 범위에 대한 이해

종료 핸들러는 그 특성상 활용 범위가 넓다. 예를 들어 파일을 개방하면 반드시 닫아줘야하고 , 커널 오브젝트의 핸들도 반드시 닫아줘야 한다 이러한 경우에 종료 핸들러의 활용은 프로그램의 안정성에도 기여를 하고, 코드의 구성도 간결히 유지하는 데에도 도움을 준다.

3.대표적인 예외 처리 방식

EXCEPTOIN_EXECUTE_HANDLER : 예외처리 루틴을 실행하라
EXCEPTION_CONTINUE_EXECUTION : 예외가 발생한 라인터부터 다시 실행

4.처리되지 않은 예외의 이동

처리되지 않은 예외는 스택 메모리 영역을 팝업해 가며 이동한다. 예외가 처리될 때가지 이동하는 특성이 있다.

5, 과도한 SEH 처리는 좋지 않다.

582

0개의 댓글