반응형

이전에 IA-32e 모드의 필수 요소인 페이징 기능을 활성화하는 방법을 알아보았습니다.

 

IA-32e 모드 커널을 작성 및 준비된 페이지 테이블을 바탕으로 페이징을 활성화하고 IA-32e 기능을 활성화한 뒤 IA-32모드로 전환하여 64비트 모드로 전환합니다.

 

IA-32e 모드로 전환하려면 거쳐야 하는 작업들의 순서는 총 7단계로 아래와 같습니다.

 

IA-32e 모드로 전환하는 7단계

 

위의 7단계는 세 가지 부분으로 나눌 수 있습니다.

● 디스크립터와 셀렉터를 갱신하는 부분

페이징을 활성화하는 부분

IA-32e 모드로 전환하는 부분

 

IA-32e 모드로 전환하는 방법을 알아보기 전에, 프로세서가 IA-32e 모드를 지원하는지부터 확인해야 합니다.

 

i386 이상의 프로세서라면 기본으로 보호 모드를 지원하므로, 현재 동작하는 거의 모든 프로세서는 보호 모드를 지원한다고 봐도 무방하지만 IA-32e 모드는 새로 추가된 프로세서 확장 기능(Processor Extended Feature)으로 모든 프로세서에서 지원한다고 보기 힘듭니다.

 


 

→ 프로세서의 제조사와 IA-32e 모드 지원 여부 검사

 

프로세서는 발전하면서 수많은 기능이 추가되었는데 그중 하나가 IA-32e 모드입니다.

 

프로세서마다 지원하는 기능이 조금씩 다르기 때문에 프로세서 제조사는 지원하는 기능을 확인할 수 있는 방법을 제공하며, 이것이 바로 CPUID 명령어입니다.

 


→ CPUID를 사용하여 프로세서 정보 확인 방법

 

x86-64 계열에는 프로세서 종류가 무수히 많고, 프로세서를 생산하는 주 업체는 인텔AMD입니다.

 

x86 계열 프로세서는 프로세서에 대한 다양한 정보를 확인할 수 있는 CPUID(CPU Identification) 명령어를 제공합니다.

 

CPUID는 EAX 레지스터에 설정된 값에 따라 해당 정보를 조회하며, 범용 레지스터 EAX, EBX, ECX, EDX를 통해 그 결과를 넘겨줍니다.

 

CPUID는 크게 기본 CPUID 정보(Basic CPUID Information)와 확장 기능 CPUID 정보(Extended Feature CPUID Information)를 제공하고, 두 정보는 다시 여러 세부 정보로 나누어집니다.

 

프로세서가 64비트 모드를 지원하는지 확인하는 것이므로 IA-32e 모드 부분을 위주로 살펴봅니다.

 

아래는 CPUID의 기능 목록 중 이번에 사용할 항목만 정리한 것입니다.

 

제조사와 64bit 지원 기능에 관련된 CPUID 기능 목록

 

위의 사진에서와 같이 0x80000001으로 확장 기능 CPUID 정보를 조회하면, EDX 레지스터의 bit 29로 64bit 지원 여부를 확인할 수 있습니다.

 

참고)

CPUID 명령어를 실행했을 때 반환되는 정보는 프로세서가 생산된 제조사(인텔, AMD 등)에 따라 다르지만, 여러 프로세서 간의 호환을 위해 공통적인 필드를 가집니다.

 

제조사별 세부 정보가 궁금하시다면 제조사의 홈페이지에 있는 CPUID 관련 문서를 참고하면 됩니다.

 


 

→ 프로세서 제조사와 IA-32e 모드 지원 여부 확인

 

CPUIDEAX, EBX, ECX, EDX 레지스터로 값을 전달하므로 어셈블리어 코드로 직접 레지스터를 제어해야 하기 때문에 보호 모드 엔트리 포인트를 위해 작성했던 PRINTMESSAGE 함수의 형태를 응용하여 C 언어에서 호출할 수 있는 CPUID 어셈블리어 함수를 만듭니다.

 

CPUID 명령어EAX 레지스터에서 조회할 정보를 넘겨받고 조회된 정보를 EAX, EBX, ECX, EDX 레지스터에 담아 넘겨주지만 만일 EAX 레지스터 값을 고정하여 특정 정보한 조회하는 함수를 만든다면 범용으로 사용할 수 없기에 조회하는 목록의 수만큼 어셈블리어 함수를 작성해야 하는데 이는 비효율적인 방법이므로 5개의 파라미터로 구성된 kReadCPUID() 함수를 작성하고, C 코드에서 이를 호출하여 부가 작업을 처리하게 합니다.

 

어셈블리어 코드로 함수를 작성한다고 해서 C 코드에서 호출할 수 없지만 이를 위해서는 어셈블리어 코드에서 해당 함수가 외부에서 사용된다는 것을 알려야 하며, global이라는 지시어가 이러한 역할을 해줍니다.

 

어셈블리어 파일 상단에 global kReadCPUID와 같이 추가하면, 링크 단계에서 kReadCPUID() 함수를 호출하는 부분과 어셈블리어 함수를 서로 연결해줍니다.

 

아래는 DWORD 타입의 EAX값과 EAX, EBX, ECX, EDX의 주소를 넘겨받아 CPUID 명령어를 실행하는 어셈블리어 코드와 그 함수의 선언부를 나타낸 것입니다.

 

주의해서 볼 부분은 kReadCPUID()의 파라미터가 변수의 포인터이기 때문에 스택에 들어있는 두 번째 파라미터부터 다섯 번째 파라미터까지는 실제 변수의 주소이므로 ESI 레지스터에 값을 옮긴 후 다시 ESI 레지스터가 가리키는 주소에 값을 저장합니다.

 

이는 C 언어의 "*pdwEAX = eax"와 같은 역할을 합니다.

 

나머지 코드는 이전에 작성했던 PRINTSTRING 함수, PRINTMESSAGE 함수와 같습니다.

 

kReadCPUID() 함수의 어셈블리 코드

 

kReadCPUID() 함수의 선언

 

제조사 정보EAX 레지스터에 0x00을 넣은 후 CPUID를 호출하면 EBX, EDX, ECX 레지스터로부터 읽을 수 있습니다.

 

제조사 문자열하위 byte에서 상위 byte의 순서로 저장되므로, 이를 문자열 버퍼로 그대로 복사하면 하나의 완성된 문자열을 얻을 수 있습니다.

 

아래는 kReadCPUID() 함수를 사용하여 제조사 문자열을 결함하는 코드입니다.

 

kReadCPUID() 함수를 사용하여 제조사 문자열을 조합하는 코드

 

IA-32e 모드 지원 여부EAX 레지스터에 0x80000001를 넣은 후, CPUID 명령어를 호출하면 EDX 레지스터의 bit 29의 값으로 판단할 수 있습니다.

 

아래는 kReadCPUID() 함수로 IA-32e 모드 지원 여부를 검사하는 코드입니다.

 

kReadCPUID() 함수로 IA-32e 모드 지원 여부를 검사하는 코드

 

실제 PC에서 위 코드를 실행했을 때 Pass로 표시된다면 IA-32e 모드를 지원하는 것이므로 안심하고 IA-32e 모드 전환을 수행해도 됩니다.

 

만약 Fail로 표시된다면 프로세서가 IA-32e 모드를 지원하지 않는 것이므로 실제 PC에서 테스트를 진행할 수 없지만

 

QEMU는 64bit에 멀티 코어 환경까지 에뮬레이션할 수 있으므로 QEM만으로도 충분합니다.

 


 

→ IA-32e 모드용 세그먼트 디스크립터 추가

 

IA-32e 모드의 세그먼트 디스크립터보호 모드의 세그먼트 디스크립터와 거의 같은 크기와 필드로 되어 있기때문에 IA-32e 모드 커널용 코드 디스크립터와 데이터 디스크립터를 생성하는 방법은 보호 모드와 거의 유사합니다.

 

차이점이라면 IA-32e 모드에서는 디스크립터의 기준 주소와 세그먼트 크기 값에 상관없이 64GB 전체 영역으로 설정된다는 것과 세그먼트 디스크립터의 L bit가 IA-32e 서브 모드 중 호환 모드 또는 64bit 모드를 선택하는 데 사용됩니다.

 

아래는 IA-32e 모드용 세그먼트 디스크립터를 나타낸 것입니다.

 

IA-32e 모드용 세그먼트 디스크립터의 구조

 

IA-32e 모드의 서브 모드 중에서 64bit 모드를 사용할 것이므로, 보호 모드용 세그먼트 디스크립터를 기반으로 L bit = 1, D/B bit = 0으로 설정하면 됩니다.

 

D/B bit를 0으로 설정하는 이유L bit와 D/B bit 모두 1인 경우를 다른 목적으로 예약해두었기 때문입니다.

 

QEMU에서는 이를 확인하지 않지만, 실제 PC에서는 L bit와 D/B bit 모두 1로 설정하면 리부팅될 수 있습니다.

 

아래의 코드는 보호 모드 엔트리 포인트 코드에 IA-32e 모드 커널용 코드 세그먼트 디스크립터와 데이터 세그먼트 디스크립터를 추가한 것입니다.

 

IA-32e 모드로 전환한 후에는 보호 모드용 세그먼트 디스크립터를 사용하지 않기 때문에 IA-32e 모드용 세그먼트 디스크립터를 앞쪽으로 배치합니다.

 

IA-32e 모드 커널용 코드와 데이터 세그먼트 디스크립터

 

IA-32e 모드용 세그먼트 디스크립터가 보호 모드용 세그먼트 디스크립터의 앞에 있으므로 보호 모드로 전환할 때 사용했던 세그먼트 셀렉터의 값을 변경해줘야 하는데

 

이전에 보호 모드용 코드와 데이트 세그먼트 디스크립터가 0x08, 0x10에 있었지만, IA-32e 모드용 코드와 데이터 디스크립터가 추가된 지금은 0x18, 0x20으로 바뀌었기에 이를 반영합니다.

 

보호 모드 커널 엔트리 포인트 파일(01.Kernel32/Source/EntryPoint.s)에서 세그먼트 디스크립터를 참조한 코드를 찾아 수정합니다.

 

세그먼트 디스크립터 오프셋 변경

 

IA-32e 모드 커널용 세그먼트 디스크립터가 준비되었으니 페이징을 활성화하고 IA-32e 모드로 전환합니다.

반응형

+ Recent posts