드디어, IA-32e모드의 문턱까지 왔다.
이제 32비트 보호모드에서 작업할 일은 없다.
오 예스~~~ 뿌듯함
기대가 앞서는구만.
현제 상태는 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비트 지원
정상적으로 출력된다.
그럼, 옵션을 바꿔 32비트만 지원하는 CPU로 바꿔보겠다.
qemu-system-x86_64 -L . -m 64 -fda disk.img -localtime -M pc -cpu n270
뭐, n270이라는 CPU가 있는데, 왠지 32비트만 지원할꺼 같아서 해봤다.
결과는 우리가 생각한 대로 나온다.
코딩 막 하다가 막 쓴 글이라 그런지 의식의 흐름대로 썻다.
스타도 눌러주시면 좋겠다.
이제 32비트 코드를 만질 일이 크게 없을것 같다. 다음부턴 64비트 커널로 점프하는
일이 남은것이겠지.
일단 지금까지 했던거 싹 정리하고 곱씹은다음 넘어가야겠다.