원문 캡처 · devblogs.microsoft.com
원문 캡처 · devblogs.microsoft.com

int 6과 ud2는 모두 2바이트 명령어지만, 예외 처리기가 받는 명령어 주소가 다르다. int 6으로 정의되지 않은 명령어 예외를 흉내 내면 처리기가 잘못된 명령어를 확인하게 되며, 사용자 모드에서는 운영체제가 int 6 실행도 허용하지 않는다.

앞서 ud2 명령어가 아키텍처상 정의되지 않은 명령어로 만들어진 이유를 다뤘다. 그렇다면 2바이트인 int 6을 사용하면 안 될까? 인터럽트 벡터 6은 잘못된 연산 코드 예외를 가리키므로, 운영체제가 정의되지 않은 명령어 실행에 부여한 동작을 직접 호출할 수 있을 것처럼 보인다.

차이는 예외 처리 방식에 있다. 아키텍처상 잘못된 연산 코드 예외는 폴트(fault)다. 폴트가 발생한 명령어의 주소가 예외 처리기에 전달된다. 반면 int 6은 트랩(trap)이므로, int 6 다음 명령어의 주소가 전달된다.

폴트는 커널이 문제를 바로잡고 해당 명령어를 다시 실행할 수 있는 오류를 나타낸다. 예를 들어 페이지 폴트는 메모리를 불러와 해결할 수 있고, 접근 위반은 페이지 권한을 바꿔 해결할 수 있다. 트랩은 이미 일어난 동작을 알리고, 실행을 재개하기 전에 그에 대응할 기회를 준다. 소프트웨어 인터럽트에서는 처리기가 인터럽트의 의미에 따라 작업한 뒤 다음 명령어부터 실행을 이어간다.

일부 폴트는 해결하기 어렵지만, 폴트로 보고되면 그 지점을 지나 실행하기 위해 별도 조치가 필요하다. 폴트를 그냥 무시하면 같은 명령어가 다시 실행돼 또 폴트가 발생한다. 예를 들어 오래된 프로세서에서 지원하지 않는 명령어를 흉내 내려면 폴트가 발생한 명령어를 살펴볼 수 있다.

int 6을 직접 호출해 #UD 정의되지 않은 명령어 폴트를 흉내 내면, 처리기는 CPU가 실제 정의되지 않은 명령어를 실행하다 폴트를 일으켰다고 간주한다. 따라서 폴트 정보에 해당 명령어의 주소가 있다고 예상한다. 하지만 int는 다음 명령어의 주소를 전달하므로 처리기는 xor eax, eax처럼 정상적인 명령어를 보고 왜 잘못된 명령어 폴트가 발생했는지 혼란을 겪을 수 있다.

또한 보호 모드에서는 int 6을 실행할 권한이 있어야 한다. 인터럽트 기술자 테이블은 각 인터럽트의 처리 방법과 명시적으로 호출할 수 있는 CPU 권한 수준을 정한다. 사용자 모드 프로그램이 int 6을 호출해 #UD 처리기를 혼란스럽게 하는 일을 막기 위해, 운영체제는 사용자 모드에 int 6 실행 권한을 주지 않는다. 따라서 #UD 정의되지 않은 명령어 폴트를 발생시키려면 실제로 정의되지 않은 명령어를 실행해야 한다.