드디어, IA-32e모드의 문턱까지 왔다.

이제 32비트 보호모드에서 작업할 일은 없다.

64비트로 전환

오 예스~~~ 뿌듯함

기대가 앞서는구만.

현제 상태는 CR0 레지스터 하나의 값만 바꾸면 64비트 운용모드로 전환되는

상태. 물론, 전환후 점프할 64비트 코드가 없기때문에, 직전에 무한루프를 걸었다.

아무튼, 이제 32비트와는 안녕~~.

일단, 64비트 모드로 전환을 위해서는

IA-32e 모드 전용으로 쓸 세그먼트 디스크립터가 필요하다.

그 다음으로는 CR4 레지스터의 PAE 비트를 1로 설정해준다.

(32비트 이상의 물리 메모리를 사용할지 여부 결정)

그 뒤로는 CR4 레지스터에 PML4 테이블의 주소를 저장해주고

IA32_EFER 레지스터의 LME 비트를 1로 바꿈.

마지막으로 CR0 레지스터의 PG비트를 1로 바꾸면서 페이징을 활성화 해버린다.

다음은 아까 지정해준 IA-32e 세그먼트 디스크립터를 세그먼트 설렉터로

지정한다음 64비트 코드로 점프.

32비트 보호모드에서 64비트 IA-32e모드로 전환하는 과정이다.

물론 해야할 작업은, CPU가 64비트를 지원하는가에 대해 검사를 해줘야 한다.

요즘 나오는 CPU는 죄다 64비트 지원하는데 옛날껀 아니라서..


#ifndef __MODESWITCH_H__
#define __MODESWITCH_H__


#include "Types.h"

/*
	Get System  CPUID
	in_eax 	: CPUID Parameter
	out_eax : Output CPUID eax register 
	out_ebx : Output CPUID ebx register
	out_ecx : Output CPUID ecx register
	out_edx : Output CPUID edx register
*/
void GetCPUID(DWORD in_eax, DWORD* out_eax,DWORD* out_ebx ,DWORD* out_ecx, DWORD* out_edx);

/*
	Switch 64Bit Kernel Mode 
	
	Set CR4 Register PAE = 1
	Set CR3 Register PML4 Table Address 0x100000
	Set IA32_EFER.LME = 1 to Enable IA-32e
	Set CR0 Register NW = 0 CD = 0 PG 1
	 
*/
void ModeSwitchAndJumpKernel64();



#endif /*__MODESWITCH_H__*/

길어보이는데 그냥 함수 두개짜리 해더파일이다.

각각 CPUID 기능을 사용하기 위한 함수와, 말그대로 64비트 모드로 전환하는 함수다.

일단 CPUID 기능으로 무엇을 하냐면, CPU제조사 이름 가져오고 이 CPU가 64비트 명령어를 지원하는가에

대한 체크다.

저 함수들의 구현이다.


[BITS 32]

global GetCPUID, ModeSwitchAndJumpKernel64


SECTION .text


; Get Cpu ID
; Following Function Use with c language 
; void GetCPUID(DWORD in_eax, DWORD* out_eax, DWORD* out_ecx, DWORD* out_edx) 
; in_eax 	: cpuid Parameter
; out_eax 	: output eax register
; out_ecx 	: output ecx register 
; out_edx	: output edx register
GetCPUID:
	push ebp
	mov ebp, esp
	push eax
	push ebx
	push ecx
	push edx
	push esi

	mov eax, dword[ebp + 8]
	cpuid
	mov esi, dword[ebp + 12]
	mov dword[esi], eax
	
	mov esi, dword[ebp + 16]
	mov dword[esi], ebx
	
	mov esi, dword[ebp + 20]
	mov dword[esi], ecx

	mov esi, dword[ebp + 24]
	mov dword[esi], edx



	pop esi
	pop edx
	pop ecx
	pop ebx
	pop eax
	pop ebp
	ret
	
; Switch IA-32e Mode And Jump 64 Bit Kernel 
; void ModeSwitchAndJumpKernel64() 
;
ModeSwitchAndJumpKernel64:
	
	
	; Set 1 CR4 Register PAE Bit
	mov eax, cr4
	or eax, 0x20
	mov cr4, eax

	mov eax, 0x100000
	mov cr3, eax
	
	mov ecx, 0xC0000080
	rdmsr
	or eax, 0x0100
	wrmsr
	
	jmp $	

	;Write Table
	mov eax, cr0
	or eax, 0xE0000000
	xor eax, 0x60000000
	mov cr0, eax
	
	

	;Not Entry
	jmp $
	

마지막에 mov eax, cr0 전에 jmp $ 걸어둔건, 점프할 64비트 커널이 없기 때문이다. 따라서

그냥 해당 위치에서 대기하도록 만듬.

그리고 GDT 수정한다음, 기존 코드에서 보호모드를 가르키던 주소를 다 변경해 주었다

GDT:
	NULLDescriptor:
	dw 0x0000
	dw 0x0000
	db 0x00
	db 0x00
	db 0x00
	db 0x00

	IA_32eCodeDescriptor:
	dw 0xFFFF	; Limit[15:0]
	dw 0x0000	; Base[15:0]
	db 0x00		; Base[23:16] 
	db 0x9A		; P = 1, DPL = 0, Code Segment, Execute/Read
	db 0xAF		; G = 1, D = 0, L = 1, Limit [19:16]
	db 0x00		; Base[31:24]
	
	IA_32eDataDescriptor:
	dw 0xFFFF	; Limit[15:0]
	dw 0x0000	; Base[15:0]
	db 0x00		; Base[23:16] 
	db 0x92		; P = 1, DPL = 0, Code Segment, Execute/Read
	db 0xAF		; G = 1, D = 0, L = 1, Limit [19:16]
	db 0x00		; Base[31:24]
	
	CODEDescriptor:
	dw 0xFFFF	; Limit[15:0]
	dw 0x0000	; Base[15:0]
	db 0x00		; Base[23:16]
	db 0x9A		; P = 1, DPL = 0, Code Segment, Execute/Read
	db 0xCF		; G = 1, D = 1, L = 0, Limit [19:16]
	db 0x00		; Base[31:24]
	
	DATADescriptor:
	dw 0xFFFF	; Limit
	dw 0x0000	; Base
	db 0x00		; Base
	db 0x92		; P=1, DPL =0, Data Segment, Read/Write
	db 0xCF		; G= 1, D= 1, L= 0, Limit
	db 0x00		; Base
GDTREND:

그리고 커널 Main함수에 추가된 코드

	PrintVideoMemory(5,8, 0x0F,"CPU Company .......................................... [            ]");
	
	DWORD out_eax, out_ebx, out_ecx, out_edx;
	GetCPUID(0x00000000, &out_eax, &out_ebx, &out_ecx, &out_edx);

	char CPU_Name[13] = {0};
	*( DWORD*)CPU_Name 		= out_ebx;
	*((DWORD*)CPU_Name+1)	= out_edx;
	*((DWORD*)CPU_Name+2)	= out_ecx;

	PrintVideoMemory(61,8, 0x0F, CPU_Name);
		
	GetCPUID(0x80000001, &out_eax, &out_ebx, &out_ecx, &out_edx);
	PrintVideoMemory(5,9, 0x0F,"Check CPU Support 64Bit ..............................");
	if(out_edx & ( 1 << 29))
	{	
		PrintVideoMemory(60,9,0x0A,"[SUCCESS]");
	}
	else
	{	
		PrintVideoMemory(60,9,0x0C,"[ERROR]");
		while(1);
	}
	
	PrintVideoMemory(5,10, 0x0F,"Now 64 Bit Mode.");
	ModeSwitchAndJumpKernel64();

CPUID를 쓸때 eax에 0을 넣으면 CPU 제조사 문자열이 나온다.

그거 맞춰서 출력해주고,

다시 CPUID를 쓰는데 이번에는 0x80000001 을 eax에 집어넣고 호출하면

각종 정보가 나오는데, 필요한건 EDX 레지스터의 29번째 비트이다.

이게 0이냐 1이냐에 따라 CPU가 64 비트를 지원하냐 마냐를 결정한다.

따라서 edx에 29째 비트와 and 연산을 해 1인지0인지 검사한다.

성공했으면 64비트 모드로 전환!

정상적으로 동작하는지 테스트를 해보았다.

일단, qemu의 default cpu는 왠지 모르지만 AMD 로 인식한다.

그래서 뒤에 옵션을 주었다.

qemu-system-x86_64 -L . -m 64 -fda disk.img -localtime -M pc -cpu Nehalem

뒤에 -cpu Nehalem 은 i7 개열 CPU를 나타낸다.

따라서 64비트 지원

정상적으로 출력된다.

64비트로 전환

그럼, 옵션을 바꿔 32비트만 지원하는 CPU로 바꿔보겠다.

qemu-system-x86_64 -L . -m 64 -fda disk.img -localtime -M pc -cpu n270

뭐, n270이라는 CPU가 있는데, 왠지 32비트만 지원할꺼 같아서 해봤다.

결과는 우리가 생각한 대로 나온다.

코딩 막 하다가 막 쓴 글이라 그런지 의식의 흐름대로 썻다.

좋은오류

FULL 소스코드는 GITHUB에 올리고 있다.

스타도 눌러주시면 좋겠다.

이제 32비트 코드를 만질 일이 크게 없을것 같다. 다음부턴 64비트 커널로 점프하는

일이 남은것이겠지.

일단 지금까지 했던거 싹 정리하고 곱씹은다음 넘어가야겠다.