Ⅰ. 프로세서의 제조사와 IA-32e 지원 여부 검사
- CPUID 명령어를 이용하여, 프로세서 제조사를 확인하는 방법과 IA-32e 모드를 지원하는지 확인하는 방법에 대해 살펴본다.
1. CPUID를 사용하여, 프로세서 정보 확인 방법
- CPUID는 EAX 레지스터에 설정된 값에 따라 해당 정보를 조회하며, 범용 레지스터 EAX, EBX, ECX, EDX를 통해 결과를 넘겨준다.
- CPUID 명령어 실행했을 때, 반환되는 정보는 프로세서가 생산된 제조자(인텔, AMD 등)에 따르지만, 여러 프로세서 간의 호환을 위해 공통적인 필드를 가지고 있다.
- EAX 값 0x00000000 일 경우, 기본 CPUID 정보를 조회한다.
- EAX 값 0x80000001 일 경우, 확장 기능 CPUID 정보를 조회한다.
2. 프로세서 제조사와 IA-32e 모드 지원 여부 확인
- 스택에 들어있는 2번째 파라미터부터 5번째 파라미터까지는 ESI 레지스터에 값을 옮긴 후, 다시 ESI 레지스터가 가리키는 어드레스에 값을 저장한다.
- 이는 kReadCPUID( )의 파라미터가 변수의 포인터이기 때문이다.
- 따라서, 스택에 있는 값은 실제 변수의 어드레스가 되므로 ESI 레지스터에 어드레스를 옮긴 후, 다시 ESI 레지스터가 가리키는 어드레스에 값을 저장하는 것이다.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; kReadCPUID( ) 함수의 어셈블리어 코드 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; C언어에서 호출할 수 있도록, 이름을 노출 (Export) global kReadCPUID SECTION .text ; CPUID를 반환 ; PARAM: DWORD dwEAX, DWORD* pdwEAX, * pdwEBX, * pdwECX, * pdwEDX kReadCPUID: push ebp ; 베이스 포인터 레지스터(EBP)를 스택에 삽입 mov ebp, esp ; 베이스 포인터 레지스터(EBP)에 스택 포인터 레지스터(ESP)의 값을 설정 push eax ; 함수에서 임시로 사용하는 레지스터로 함수의 마지막 부분에서 push ebx ; 스택에 삽입된 값을 꺼내 원래 값으로 복원 push ecx push edx push esi ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; EAX 레지스터의 값으로 CPUID 명령어 실행 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; mov eax, dword [ebp + 8] ; 파라미터 1(dwEAX)를 EAX 레지스터에 저장 CPUID ; CPUID 명령어 실행 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; 반환된 값을 파라미터에 저장 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; *pdwEAX mov esi, dword [ebp + 12] ; 파라미터 2(pdwEAX)를 ESI 레지스터에 저장 mov dword [esi], eax ; pdwEAX가 포인터이므로, 포인터가 가리키는 어드레스에 ; EAX 레지스터의 값을 저장 ; *pdwEBX mov esi, dword [ebp + 16] ; 파라미터 3(pdwEBX)를 ESI 레지스터에 저장 mov dword [ esi ], ebx ; pdwEBX가 포인터이므로 포인터가 가리키는 어드레스에 ; EBX 레지스터의 값을 저장 ; *pdwECX mov esi, dword [ ebp + 20 ] ; 파라미터 4(pdwECX)를 ESI 레지스터에 저장 mov dword [ esi ], ecx ; pdwECX가 포인터이므로 포인터가 가리키는 어드레스에 ; ECX 레지스터의 값을 저장 ; *pdwEDX mov esi, dword [ ebp + 24 ] ; 파라미터 5(pdwEDX)를 ESI 레지스터에 저장 mov dword [ esi ], edx ; pdwEDX가 포인터이므로 포인터가 가리키는 어드레스에 ; EDX 레지스터의 값을 저장 pop esi ; 함수에서 사용이 끝난 ESI 레지스터부터 EBP 레지스터까지를 스택에 pop edx ; 삽입된 값을 이용해서 복원 pop ecx ; 스택은 가장 마지막에 들어간 데이터가 가장 먼저 나오는 pop ebx ; 자료구조(Last-In, First-Out)이므로 삽입(push)의 역순으로 pop eax ; 제거(pop) 해야 함 pop ebp ; 베이스 포인터 레지스터(EBP) 복원 ret ; 함수를 호출한 다음 코드의 위치로 복귀
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; kReadCPUID( ) 함수의 선언 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; void kReadCPUID( DWORD dwEAX, DWORD* pdwEAX, DWORD* pdwEBX, DWORD* pdwECX, DWORD* pdwEDX );
- kReadCPUID( ) 함수를 사용하여, 제조사 문자열을 조합하는 코드
DWORD dwEAX, dwEBX, dwECX, dwEDX;
char vcVendorString[13] = {0, };
// 프로세서 제조사 정보 읽기
kReadCPUID (0x00, &dwEAX, &dwEBX, &dwECX, &dwEDX);
*(DWORD*) vcVemdprString = dwEBX;
*((DWORD*) vcVemdprString + 1) = dwEDX;
*((DWORD*) vcVemdprString + 2) = dwECX;
// 제조사 문자열이 출력됨
kPrintString(0, 0, vcVendorString);
- kReadCPUID( )함수로, IA-32e 모드 지원 여부를 검사하는 코드
- EAX 레지스터에 0x80000001를 넣은 후, CPUID 명령어를 호출하면 EDX 레지스터의 비트 29의 값으로 판단할 수 있다.
DWORD dwEAX, dwEBX, dwECX, dwEDX; // 64비트 지원 여부 확인 kReadCPUID (0x80000001, &dwEAX, &dwEBX, &dwECX, &dwEDX); if(dwEDX & (1 << 29)){ kPrintString(0, 0, "Pass"); } else{ kPrintString(0, 0, "Fail"); }
Ⅱ. IA-32e 모드용 세그먼트 디스크립터 추가
1. 보호 모드 커널 엔트리 포인트에 디스크립터 추가
- IA-32e 모드의 서브 모드 중, 64bit 모드를 사용할 것이므로 보호 모드용 세그먼트 디스크립터를 기반으로 L비트를 1로 D비트를 0으로 설정하면 된다.
- D비트를 0으로 설정하는 이유는 L비트와 D비트가 모두 1인 경우를 다른 목적으로 예약해두었기 때문이다.
- (실제 PC의 경우, L비트와 D비트를 모두 1로 설정하면 리부팅할 수 있으니 주의해야한다.)
- IA-32e 모드 커널용 코드와 데이터 세그먼트 디스크립터 코드
- IA-32e 모드 전환 후, 보호 모드용 세그먼트 디스크립터를 사용하지 않으므로 IA-32e 모드용 세그먼트 디스크립터를 앞쪽으로 배치하였다.
; GDT 테이블 정의 GDT: ; 널(NULL) 디스크립터, 반드시 0으로 초기화해야 함 NULLDescriptor: dw 0x0000 dw 0x0000 db 0x00 db 0x00 db 0x00 db 0x00 ; IA-32e 모드 커널용 코드 세그먼트 디스크립터 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-32e 모드 커널용 데이터 세그먼트 디스크립터 IA_32eDATADESCRIPTOR: dw 0xFFFF ; Limit [15:0] dw 0x0000 ; Base [15:0] db 0x00 ; Base [23:16] db 0x92 ; P=1, DPL=0, Data Segment, Read/Write 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 [15:0] dw 0x0000 ; Base [15:0] db 0x00 ; Base [23:16] db 0x92 ; P=1, DPL=0, Data Segment, Read/Write db 0xCF ; G=1, D=1, L=0, Limit[19:16] db 0x00 ; Base [31:24] GDTEND:
- 세그먼트 디스크립터 오프셋 변경
- IA-32e 모드용 세그먼트 디스크립터가 보호 보드용 세그먼트 디스크립터 앞에 있으므로 보호 모드로 전환할 때, 세그먼트 셀렉터의 값을 변경해줘야 한다.
- IA-32e 모드용 코드와 데이터 디스크립터는 0x18, 0x20 이다.
- 해당 코드는 (1.Kernel32/Source/EntryPoint.s)에서 세그먼트 디스크립터를 참조한 코드를 찾아 수정한다.
[ORG 0x00] ; 코드의 시작 어드레스를 0x00으로 설정 [BITS 16] ; 이하의 코드는 16비트 코드로 설정 SECTION .text ; text 섹션(세그먼트)을 정의 START: ``` 생략 ``` ; 커널 코드 세그먼트를 0x00을 기준으로 하는 것으로 교체하고 EIP의 값을 0x00을 기준으로 재설정 ; CS 세그먼트 셀렉터 : EIP jmp dword 0x18: ( PROTECTEDMODE - $$ + 0x10000 ) [BITS 32] ; 이하의 코드는 32비트 코드로 설정 PROTECTEDMODE: mov ax, 0x20 ; 보호 모드 커널용 데이터 세그먼트 디스크립터를 AX 레지스터에 저장 mov ds, ax ; DS 세그먼트 셀렉터에 설정 mov es, ax ; ES 세그먼트 셀렉터에 설정 mov fs, ax ; FS 세그먼트 셀렉터에 설정 mov gs, ax ; GS 세그먼트 셀렉터에 설정 ``` 생략 ``` jmp dword 0x18: 0x10200 ; C 언어 커널이 존재하는 0x10200 어드레스로 이동하여 C 언어 커널 수행Ⅲ
Ⅲ. IA-32e 모드 전환과 1차 정리
1. 물리 메모리 확장 기능 활성화와 페이지 테이블 설정
- 물리 메모리 확장 기능은 ‘CR4 레지스터의 PAE 비트(비트5)’가 담당하고 있으며, PAE 비트를 1로 설정해서 물리 메모리 확장 기능을 사용할 수 있었다.
- 프로세서에 페이지 테이블을 설정하려면, CR3 레지스터에 PML4 테이블의 어드레스를 저장하면 된다.
- 아래는 CR3, CR4 레지스터를 조작하여, 페이지 테이블을 설정하고 물리 메모리 확장 기능을 활성화하는 코드이다.
; CR4 컨트롤 레지스터의 PAE 비트를 1로 설정
mov eax, cr4 ; CR4 컨트롤 레지스터의 값을 EAX 레지스터에 저장
or eax, 0x20 ; PAE 비트(비트5)를 1로 설정
mov cr4, eax ; PAE 비트가 1로 설정된 값을 CR4 컨트롤 레지스터에 저장
; CR3 컨트롤 레지스터에 PML4 테이블의 어드레스 및 캐시 활성화
mov eax, 0x100000 ; EAX 레지스터에 PML4 테이블이 존재하는 0x1000000(1MB)를 저장
mov cr3, eax ; CR3 컨트롤 레지스터에 0x100000(1MB)를 저장
2. 64비트 모드 활성화와 페이징 활성화
- IA-32e모드를 활성화하는 실질적인 것은 IA32_EFER레지스터의 LME비트를 1로 설정하는 것이다.
- IA32_EFER 레지스터의 비트를 활성화하지 않으면, IA-32e 모드용 세그먼트 레지스터로 교체하더라도 32비트 보호모드로만 동작하기 때문이다.
- IA32_EFER레지스터는 MSR(Model-Specific Register)라고 불리는 특수한 용도 레지스터이다.
- MSR레지스터
- 프로세서 모델에 따라, 특수하게 정의된 레지스터이다.
- 크기는 64bit이며, 각 모델에 따라 차이가 존재한다.
- 종류는 디버깅 및 성능 측정, 하드웨어 에러 검사, 메모리 범위와 메모리 타입 설정, 온도와 전력 관리, 특수 명령어 지원, 프로세서 특성과 모드 지원 등 6가지 종류가 존재한다.
- IA-32e모드 전환에 사용할 IA32_EFER 레지스터는 프로세서 특성과 모드 지원에 속하는 MSR 레지스터로 프로세서의 확장 기능을 제어할 수 있는 레지스터이다.
- IA32_EFER 레지스터로 제어할 수 있는 항목은 SYSCALL/SYSRET 사용, IA-32e 모드 사용, Execute Disable(EXB) 사용 등이 있다.
- 제어 기능 외에 현재 운영 중인 모드가 IA-32e 모드인지 확인하는 기능도 포함한다.
- IA32_EFER 레지스터를 변경하여, IA-32 모드를 활성화하는 코드
- IA32_EFER 레지스터는 0xC0000080 어드레스에 있다.
- LME 비트를 1로 설정하는 코드이며, LME비트는 읽기, 쓰기 모드를 지원한다.
; IA32_EFER LME를 1로 설정하여, IA-32e 모드를 활성화 mov ecx, 0xC0000080 ; IA32_EFER MSR 레지스터의 어드레스를 저장 rdmsr ; MSR 레지스터를 읽기 or eax, 0x0100 ; EAX 레지스터에 저장된 IA32_EFER MSR의 하위 32bit에서 ; LME 비트(비트 8)을 1로 설정 wrmsr ; MSR 레지스터에 쓰기
3. IA-32e 모드로 전환과 세그먼트 셀렉터 초기화
- IA-32e 모드로 전환하는 마지막 작업은 CR0 레지스터를 변경하여 캐시와 페이징을 활성화하고 세그먼트 셀렉터를 IA-32e 커널용으로 교체하는 것이다.
- CR0 레지스터를 변경하여, 페이징을 활성화하고 IA-32e 모드로 전환하여 세그먼트 셀렉터를 초기화하는 코드
- 이전 IA-32e 모드 커널용 코드 세그먼트 디스크립터와 데이터 세그먼트 디스크립터를 GDT 테이블의 오프셋 0x08, 0x10에 생성했으므로 해당값을 참고하여 세그먼트 셀렉터를 초기화하였다.
; CR0 컨트롤 레지스터를 NW 비트(비트 29) = 0, CD 비트(비트 30) = 0, PG 비트(비트 31) = 1로 ; 설정하여 캐시 기능과 페이징 기능을 활성화 mov eax, cr0 ; EAX 레지스터에 CR0 컨트롤 레지스터를 저장 or eax, 0xE0000000 ; NW 비트(비트 29), CD 비트(비트 30), PG 비트(비트 31)을 모두 1로 설정 xor eax, 0x60000000 ; NW 비트(비트 29)와 CD 비트(비트 30)을 XOR하여 0으로 설정 mov cr0, eax ; NW 비트 = 0, CD 비트 = 0, PG 비트 = 1로 설정한 값을 다시 ; CR0 컨트롤 레지스터에 저장 jmp 0x08:0x200000 ; CS 세그먼트 셀렉터를 IA-32e 모드용 코드 세그먼트 디스크립터로 ; 교체하고 0x200000(2MB) 어드레스로 이동 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; 0x200000(2MB) 어드레스에 위치하는 코드 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; [BITS 64] ; 이하의 코드는 64비트 코드로 설정 ; 기타 세그먼트 셀렉터를 IA-32e 모드용 데이터 세그먼트 디스크립터로 교체 mov ax, 0x10 ; IA-32e 모드 커널용 데이터 세그먼트 디스크립터를 AX 레지스터에 저장 mov ds, ax ; DS 세그먼트 셀렉터에 설정 mov es, ax ; ES 세그먼트 셀렉터에 설정 mov fs, ax ; FS 세그먼트 셀렉터에 설정 mov gs, ax ; GS 세그먼트 셀렉터에 설정 ; 스택을 0x600000 ~ 0x6FFFFF 영역에 1MB 크기로 생성 mov ss, ax ; SS 세그먼트 셀렉터에 설정 mov rsp, 0x6FFFF8 ; RSP 레지스터의 어드레스를 0x6FFFF8로 설정 mov rbp, 0x6FFFF8 ; RBP 레지스터의 어드레스를 0x6FFFF8로 설정 ```생략```
4. 소스코드 1차 정리와 실행
(1) 모드 전환과 관련된 어셈블리어 코드 파일 추가
- (1.Kernel32/Source) 위치에 ModeSwitch.asm과 ModeSwitch.h를 작성한다.
- ModeSwitch.asm
[BITS 32] ; 이하의 코드는 32비트 코드로 설정
; C 언어에서 호출할 수 있도록 이름을 노출함(Export)
global kReadCPUID, kSwitchAndExecute64bitKernel
SECTION .text ; text 섹션(세그먼트)을 정의
; CPUID를 반환
; PARAM: DWORD dwEAX, DWORD* pdwEAX,* pdwEBX,* pdwECX,* pdwEDX
kReadCPUID:
push ebp ; 베이스 포인터 레지스터(EBP)를 스택에 삽입
mov ebp, esp ; 베이스 포인터 레지스터(EBP)에 스택 포인터 레지스터(SP)의 값을 설정
push eax ; 함수에서 임시로 사용하는 레지스터로 함수의 마지막 부분에서
push ebx ; 스택에 삽입된 값을 꺼내 원래 값으로 복원
push ecx
push edx
push esi
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; EAX 레지스터의 값으로 CPUID 명령어 실행
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
mov eax, dword [ ebp + 8 ] ; 파라미터 1(dwEAX)를 EAX 레지스터에 저장
cpuid ; CPUID 명령어 실행
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 반환된 값을 파라미터에 저장
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; *pdwEAX
mov esi, dword [ ebp + 12 ] ; 파라미터 2(pdwEAX)를 ESI 레지스터에 저장
mov dword [ esi ], eax ; pdwEAX가 포인터이므로 포인터가 가리키는 어드레스에
; EAX 레지스터의 값을 저장
; *pdwEBX
mov esi, dword [ ebp + 16 ] ; 파라미터 3(pdwEBX)를 ESI 레지스터에 저장
mov dword [ esi ], ebx ; pdwEBX가 포인터이므로 포인터가 가리키는 어드레스에
; EBX 레지스터의 값을 저장
; *pdwECX
mov esi, dword [ ebp + 20 ] ; 파라미터 4(pdwECX)를 ESI 레지스터에 저장
mov dword [ esi ], ecx ; pdwECX가 포인터이므로 포인터가 가리키는 어드레스에
; ECX 레지스터의 값을 저장
; *pdwEDX
mov esi, dword [ ebp + 24 ] ; 파라미터 5(pdwEDX)를 ESI 레지스터에 저장
mov dword [ esi ], edx ; pdwEDX가 포인터이므로 포인터가 가리키는 어드레스에
; EDX 레지스터의 값을 저장
pop esi ; 함수에서 사용이 끝난 ESI 레지스터부터 EBP 레지스터까지를 스택에
pop edx ; 삽입된 값을 이용해서 복원
pop ecx ; 스택은 가장 마지막에 들어간 데이터가 가장 먼저 나오는
pop ebx ; 자료구조(Last-In, First-Out)이므로 삽입(push)의 역순으로
pop eax ; 제거(pop) 해야 함
pop ebp ; 베이스 포인터 레지스터(EBP) 복원
ret ; 함수를 호출한 다음 코드의 위치로 복귀
; IA-32e 모드로 전환하고 64비트 커널을 수행
; PARAM: 없음
kSwitchAndExecute64bitKernel:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; CR4 컨트롤 레지스터의 PAE 비트를 1로 설정
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
mov eax, cr4 ; CR4 컨트롤 레지스터의 값을 EAX 레지스터에 저장
or eax, 0x20 ; PAE 비트(비트 5)를 1로 설정
mov cr4, eax ; PAE 비트가 1로 설정된 값을 CR4 컨트롤 레지스터에 저장
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; CR3 컨트롤 레지스터에 PML4 테이블의 어드레스 및 캐시 활성화
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
mov eax, 0x100000 ; EAX 레지스터에 PML4 테이블이 존재하는 0x100000(1MB)를 저장
mov cr3, eax ; CR3 컨트롤 레지스터에 0x100000(1MB)을 저장
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; IA32_EFER.LME를 1로 설정하여 IA-32e 모드를 활성화
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
mov ecx, 0xC0000080 ; IA32_EFER MSR 레지스터의 어드레스를 저장
rdmsr ; MSR 레지스터를 읽기
or eax, 0x0100 ; EAX 레지스터에 저장된 IA32_EFER MSR의 하위 32비트에서
; LME 비트(비트 8)을 1로 설정
wrmsr ; MSR 레지스터에 쓰기
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; CR0 컨트롤 레지스터를 NW 비트(비트 29) = 0, CD 비트(비트 30) = 0, PG 비트(비트 31) = 1로
; 설정하여 캐시 기능과 페이징 기능을 활성화
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
mov eax, cr0 ; EAX 레지스터에 CR0 컨트롤 레지스터를 저장
or eax, 0xE0000000 ; NW 비트(비트 29), CD 비트(비트 30), PG 비트(비트 31)을 모두 1로 설정
xor eax, 0x60000000 ; NW 비트(비트 29)와 CD 비트(비트 30)을 XOR하여 0으로 설정
mov cr0, eax ; NW 비트 = 0, CD 비트 = 0, PG 비트 = 1로 설정한 값을 다시
; CR0 컨트롤 레지스터에 저장
jmp 0x08:0x200000 ; CS 세그먼트 셀렉터를 IA-32e 모드용 코드 세그먼트 디스크립터로
; 교체하고 0x200000(2MB) 어드레스로 이동
; 여기는 실행되지 않음
jmp $
- ModeSwitch.h
#ifndef __MODESWITCH_H__
#define __MODESWITCH_H__
#include "Types.h"
void kReadCPUID( DWORD dwEAX, DWORD* pdwEAX, DWORD* pdwEBX, DWORD* pdwECX, DWORD* pdwEDX );
void kSwitchAndExecute64bitKernel( void );
#endif
(2) 보호 모드 커널의 엔트리 포인트 파일 수정
- 보호 모드 커널의 엔트리 포인트 파일에는 IA-32e 모드 커널용 세그먼트 디스크립터가 추가되었다.
- (1.Kernel32/Source) 위치의 EntryPoint.s 코드를 수정한다.
[ORG 0x00] ; 코드의 시작 어드레스를 0x00으로 설정
[BITS 16] ; 이하의 코드는 16비트 코드로 설정
SECTION .text ; text 섹션(세그먼트 정의)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 코드 영역
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
START:
mov ax, 0x1000 ; 보호 모드 엔트리 포인트의 시작 어드레스를 세그먼트 레지스터 값으로 변환
mov ds, ax ; DS 세그먼트 레지스터에 설정
mov es, ax ; ES 세그먼트 레지스터에 설정
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; A20 게이트를 활성화
; BIOS를 이용한 전환이 실패했을 때, 시스템 컨트롤 포트로 전환 시도
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; BIOS 서비스를 사용해서, A20 게이트를 활성화
mov ax, 0x2401 ; A20 게이트 활성화 서비스 설정
int 0x15 ; BIOS 인터럽트 서비스 호출
jc .A20GATERROR ; A20 게이트 활성화가 성공했는지 확인
jmp .A20GATESUCCESS
.A20GATERROR:
; 에러 발생 시, 시스템 컨트롤 포트로 전환 시도
in al, 0x92 ; 시스템 컨트롤 포트(0x92)에서 1byte를 읽어 AL 레지스터에 저장
or al, 0x02 ; 읽은 값에 A20 게이트 비트(비트 1)를 1로 설정
and al, 0xFE ; 시스템 리셋 방지를 위해 0xFE와 AND연산하여 비트 0을 0으로 설정
out 0x92, al ; 시스템 컨트롤 포트(0x92)에 변경된 값을 1byte 설정
.A20GATESUCCESS:
cli ; 인터럽트가 발생하지 못하도록 설정
lgdt [ GDTR ] ; GDTR 자료구조를 프로세서에 설정하여, GDT 테이블을 로드
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 보호 모드로 진입
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
mov eax, 0x4000003B ;PG=0, CD=1, NW=0, AM=0, WP=0, NE=1, ET=1, TS=1, EM=0, MP=1, PE=1
mov cr0, eax ;CR0 컨트롤 레지스터에 위에서 저장한 플래그를 설정하여 보호모드로 전환
; 커널 코드 세그먼트를 0x00을 기준으로 하는 것으로 교체하고 EIP의 값을 0x00을 기준으로 재설정
; CS 세그먼트 셀렉터: EIP
jmp dword 0x18: ( PROTECTEDMODE - $$ + 0x10000 )
; 커널 코드 세그먼트가 0x00을 기준으로 하는 반면, 실제 코드는 0x10000을 기준으로 실행되므로, 오프셋에 0x10000를 더해서 세그먼트 교체 후에도 같은 선형 주소를 가리키게 함
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 보호 모드로 진입
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
[BITS 32] ; 이하의 코드는 32bit 코드로 설정
PROTECTEDMODE:
mov ax, 0x20 ;보호 모드 커널용 데이터 세그먼트 디스크립터를 AX 레지스터에 저장
mov ds, ax ;DS 세그먼트 셀렉터에 설정
mov es, ax ;ES 세그먼트 셀렉터에 설정
mov fs, ax ;FS 세그먼트 셀렉터에 설정
mov gs, ax ;GS 세그먼트 셀렉터에 설정
; 스택을 0x00000000~0x0000FFFF 영역에 64KB 크기로 생성
mov ss, ax ; SS 세그먼트 셀렉터에 설정
mov esp, 0xFFFE ; ESP 레지스터의 어드레스를 0xFFFE로 설정
mov ebp, 0xFFFE ; EBP 레지스터의 어드레스를 0xFFFE로 설정
; 화면에 보호 모드로 전환되었다는 메시지를 찍는다.
push ( SWITCHSUCCESSMESSAGE - $$ + 0x10000 ) ; 출력할 메시지의 어드레스르 스택에 삽입
push 2 ; 화면 Y 좌표(2)를 스택에 삽입
push 0 ; 화면 X 좌표(0)를 스택에 삽입
call PRINTMESSAGE ; PRINTMESSAGE 함수 호출
add esp, 12 ; 삽입한 파라미터 제거
jmp dword 0x18: 0x10200 ; C언어 커널이 존재하는 0x10200 어드레스로 이동하여 C언어 커널 수행
; CS 세그먼트 셀렉터를 커널 코드 디스크립터(0x08)로 변경
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 함수 코드 영역
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;메시지 출력하는 함수 (PARAM: x좌표, y좌표, 문자열)
PRINTMESSAGE:
push ebp ;베이스 포인터 레지스터(BP)를 스택에 삽입
mov ebp, esp ;베이스 포인터 레지스터(BP)에 스택 포인터 레지스터(SP)의 값 설정
push esi ;함수에서 임시로 사용하는 레지스터로 함수의 마지막 부분에서
push edi ;스택에 삽입된 값을 꺼내 원래 값으로 복원
push eax
push ecx
push edx
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; X, Y의 좌표로 비디오 메모리의 어드레스를 계산함
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Y좌표를 이용해서 라인 어드레스를 먼저 구함
mov eax, dword [ebp+12] ;파라미터 2(화면 Y좌표)를 EAX레지스터에 설정
mov esi, 160 ;한 라인의 바이트 수(2*80 컬럼)을 ESI레지스터에 설정
mul esi ;EAX레지스터와 ESI레지스터를 곱하여, 화면 Y어드레스 계산
mov edi, eax ;계산된 화면 Y어드레스를 EDI레지스터에 설정
;X좌표를 이용해서 2를 곱한 후, 최종 어드레스 구함
mov eax, dword [ebp+8] ;파라미터 1(화면 X좌표)를 EAX레지스터에 설정
mov esi, 2 ;한 문자를 나타내는 바이트수(2)를 ESI레지스터에 설정
mul esi ;EAX레지스터와 ESI레지스터를 곱하여, 화면 X어드레스를 계산
add edi, eax ;화면 y어드레스와 계산된 X 어드레스를 더해서 실제 비디오 메모리 어드레스 계산
; 출력한 문자열의 어드레스
mov esi, dword [ebp+16] ;파라미터 3(출력한 문자열의 어드레스)
.MESSAGELOOP: ;메시지를 출력하는 루프
mov cl, byte [esi] ;ESI레지스터가 가르키는 문자열 위치에 한 문자를 CL레지스터에 복사.
;CL레지스터는 CX레지스터의 하위 1byte를 의미
;문자열은 1byte면 충분하므로 ECX레지스터의 하위 1byte만 사용
cmp cl, 0 ;복사된 문자와 0을 비교
je .MESSAGEEND ;복사한 문자의 값이 0이면 문자열이 종료되었음을 의미하며, .MESSAGEEND로 이동하여 문자 출력 종료
mov byte [edi + 0xB8000], cl ;0이 아니라면 비디오 메모리 어드레스 0xB800:di에 문자를 출력
add esi, 1 ;ESI레지스터에 1을 더하여, 다음 문자열로 이동
add edi, 2 ;EDI레지스터에 2을 더하여, 비디오 메모리의 다음 문자 위치로 이동
;비디오 메모리는 (문자, 속성)의 쌍으로 구성되므로 문자만 출력하려면 2를 더해야함
jmp .MESSAGELOOP ;메시지 출력 루프로 이동하여 다음 문자 출력
.MESSAGEEND:
pop edx ;함수에서 사용이 끝난 EDX 레지스터부터 EBP레지스터까지 스택에 삽입된 값을 이용해서 복원
pop ecx ;스택은 가장 마지막에 들어간 데이터가 가장 먼저 나오는 자료구조이므로
pop eax ;삽입의 역순으로 제거해야함
pop edi
pop esi
pop ebp ;베이스 포인터 레지스터(BP) 복원
ret ;함수를 호출한 다음 코드의 위치로 복원
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 데이터 영역
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 아래 데이터들을 8byte에 맞춰 정렬하기 위해 추가
align 8, db 0
;GDTR의 끝을 8byte로 정렬하기 위해 추가
dw 0x10000
;GDTR 자료구조 정의
GDTR:
dw GDTEND - GDT - 1 ; 아래에 위치한 GDT 테이블의 전체 크기
dd (GDT - $$ + 0x10000) ; 아래에 위치한 GDT 테이블의 시작 어드레스
;GDT 테이블 정의
GDT:
; 널 디스크립터. 반드시 0으로 초기화해야함
NULLDescriptor:
dw 0x0000
dw 0x0000
db 0x00
db 0x00
db 0x00
db 0x00
; IA-32e 모드 커널용 코드 세그먼트 디스크립터
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-32e 모드 커널용 데이터 세그먼트 디스크립터
IA_32eDATADESCRIPTOR:
dw 0xFFFF ; Limit [15:0]
dw 0x0000 ; Base [15:0]
db 0x00 ; Base [23:16]
db 0x92 ; P=1, DPL=0, Data Segment, Read/Write
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 [15:0]
dw 0x0000 ; Base [15:0]
db 0x00 ; Base [23:16]
db 0x92 ; P=1, DPL=0, Data Segment, Read/Write
db 0xCF ; G=1, D=1, L=0, Limit[19:16]
db 0x00 ; Base [31:24]
GDTEND:
;보호모드로 전환되었다는 메시지
SWITCHSUCCESSMESSAGE: db 'Switch To Protected Mode Success~!!', 0
times 512 - ($ - $$) db 0x00 ;512byte를 맞추기 위해, 남은 부분을 0으로 채움
(3) 보호 모드 커널의 C언어 엔트리 포인트 파일 수정
- IA-32e 모드 지원 여부를 검사하는 기능과 IA-23e 모드로 전환하는 기능이 추가되었으니, 보호 모드 커널의 C언어 엔트리 포이느 함수에서 이를 호출해야한다.
- 하지만, IA-32e 모드 커널이 존재한다면 IA-32e 모드 전환 함수를 호출할 수 있지만 아직 준비중이므로 해당 부분에서는 주석으로 처리해 놓을 예정이다.
- (1.Kernel32/Source) 위치의 Main.c 코드에 추가한다.
#include "Types.h"
#include "Page.h"
#include "ModeSwitch.h"
void kPrintString (int iX, int iY, const char* pcString);
BOOL kInitializeKernel64Area( void );
BOOL kIsMemoryEnough( void );
//Main 함수
void Main(void){
DWORD i;
DWORD dwEAX, dwEBX, dwECX, dwEDX;
char vcVendorString[13] = {0, };
kPrintString( 0, 3, "C Language Kernel Start.....................[Pass]");
// 최소 메모리 크기를 만족하는지 검사
kPrintString(0, 4, "Minimum Memory Size Chek....................[ ]");
if(kIsMemoryEnough() == FALSE){
kPrintString(45, 4, "Fail");
kPrintString(0, 5, "Not Enough Memory~!! MINT64 OS Requires Over " "64Mbyte Memory~!!");
while(1);
}else{
kPrintString(45, 4, "Pass");
}
// IA-32e 모드의 커널 영역을 초기화
kPrintString(0, 5, "IA-32e Kernel Area Initialization...........[ ]");
if(kInitializeKernel64Area() == FALSE){
kPrintString(45, 5, "Fail");
kPrintString(0, 6, "Kernel Area Initialization Fail~!!");
while(1);
}
kPrintString(45, 5, "Pass");
// IA-32e 모드 커널을 위한 페이지 테이블 생성
kPrintString(0, 6, "IA-32e Page Tables Initialization...........[ ]");
kInitializePageTables();
kPrintString(45, 6, "Pass");
// 프로세서 제조사 정보 읽기
kReadCPUID(0x00, &dwEAX, &dwEBX, &dwECX, &dwEDX);
*( DWORD* ) vcVendorString = dwEBX;
*(( DWORD* ) vcVendorString + 1 ) = dwEDX;
*(( DWORD* ) vcVendorString + 2 ) = dwECX;
kPrintString(0, 7, "Processor Vendor String.....................[ ]");
kPrintString(45, 7, vcVendorString );
// 64비트 지원 유무 확인
kReadCPUID(0x80000001, &dwEAX, &dwEBX, &dwECX, &dwEDX);
kPrintString(0, 8, "64bit Mode Support Check....................[ ]");
if(dwEDX & (1 << 29))
{
kPrintString(45, 8, "Pass");
}
else
{
kPrintString(45, 8, "Fail");
kPrintString(0, 9, "This processor does not support 64bit mode~!!");
while(1);
}
// IA-32e 모드로 전환
kPrintString(0, 9, "Switch To IA-32e Mode");
// 원래는 아래 함수를 호출해야 하나 IA-32e 모드 커널이 없으므로 주석 처리
//kSwitchAndExecute64bitKernel();
while(1) ;
}
```생략```
Ⅳ. IA-32e 모드용 커널 준비
- IA-32e 모드 커널을 생성한다.
- 보호 모드 커널의 소스 파일을 기반으로 2.Kernel64 디렉터리에 IA-32e 모드 커널의 소스 파일을 생성한다.
1. 커널 엔트리 포인트 파일 생성
- (2.Kernel64/Source)의 EntryPoint.s를 수정한다.
[BITS 64] ; 이하의 코드는 64비트 코드로 설정
SECTION .text ; text 섹션(세그먼트)을 정의
; 외부에서 정의된 함수를 쓸 수 있도록 선언함(Import)
extern Main
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 코드 영역
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
START:
mov ax, 0x10 ;IA-32e 모드 커널용 데이터 세그먼트 디스크립터를 AX 레지스터에 저장
mov ds, ax ;DS 세그먼트 셀렉터에 설정
mov es, ax ;ES 세그먼트 셀렉터에 설정
mov fs, ax ;FS 세그먼트 셀렉터에 설정
mov gs, ax ;GS 세그먼트 셀렉터에 설정
; 스택을 0x600000~0x6FFFFF 영역에 1MB 크기로 생성
mov ss, ax ; SS 세그먼트 셀렉터에 설정
mov rsp, 0x6FFFF8 ; RSP 레지스터의 어드레스를 0x6FFFF8로 설정
mov rbp, 0x6FFFF8 ; RBP 레지스터의 어드레스를 0x6FFFF8로 설정
call Main ; C언어 엔트리 포인트 함수(Main) 호출
jmp $
(1) C언어 엔트리 포인트 파일 생성
- (2.Kernel64/Source/Types.h)는 보호모드 파일과 같으므로, 보호모드용 파일을 복사하여 넣는다.
- (2.Kernel64/Source/)의 Main.c 파일을 작성한다.
#include "Types.h"
// 함수 선언
void kPrintString (int iX, int iY, const char* pcString);
// 아래 함수는 C언어 커널의 시작 부분임
void Main(void){
kPrintString(0, 10, "Switch To IA-32e Mode Success~!!");
kPrintString(0, 11, "IA-32e C Language Kernel Start..............[Pass]");
}
//문자열 출력 함수
void kPrintString(int iX, int iY, const char* pcString){
CHARACTER* pstScreen = (CHARACTER*) 0xB8000;
int i;
pstScreen += (iY * 80) + iX;
for(i = 0; pcString[i] != 0; i++){
pstScreen[i].bCharactor = pcString [i];
}
}
(2) 링크 스크립트 파일 생성
- IA-32e 커널은 라이브러리를 사용하지 않도록 빌드해야 한다.
- 0x200000(2MB) 어드레스로 복사되어 실행될 것이므로, 커널 이미지를 생성할 때 고려해야 한다.
- IA-32e 모드용 커널을 빌드해야 하므로, 64bit 이미지에 관련된 elf_x86_64.x 파일을 기반으로 진행된다.
- (2.Kernel64)에 elf_x86_64.x 파일을 생성한다.
/* Default linker script, for normal executables */
OUTPUT_FORMAT("elf64-x86-64", "elf64-x86-64", "elf64-x86-64")
OUTPUT_ARCH(i386:x86-64)
ENTRY(_start)
SEARCH_DIR("/usr/cross/x86_64-pc-linux/lib64"); SEARCH_DIR("/usr/cross/x86_64-pc-linux/lib");
SECTIONS
{
/* Read-only sections, merged into text segment: */
PROVIDE (__executable_start = 0x400000); . = 0x400000 + SIZEOF_HEADERS;
/*********************************************************************************/
/* 섹션 재비치로 인해 앞으로 이동된 부분 */
.text 0x200000 :
{
*(.text .stub .text.* .gnu.linkonce.t.*)
/* .gnu.warning sections are handled specially by elf32.em. */
*(.gnu.warning)
} =0x90909090
.rodata : { *(.rodata .rodata.* .gnu.linkonce.r.*) }
.rodata1 : { *(.rodata1) }
/* 데이터 영역의 시작을 섹터 단위로 맞춤 */
. = ALIGN (512);
.data :
{
*(.data .data.* .gnu.linkonce.d.*)
SORT(CONSTRUCTORS)
}
.data1 : { *(.data1) }
__bss_start = .;
.bss :
{
*(.dynbss)
*(.bss .bss.* .gnu.linkonce.b.*)
*(COMMON)
/* Align here to ensure that the .bss section occupies space up to
_end. Align after .bss to ensure correct alignment even if the
.bss section disappears because there are no input sections.
FIXME: Why do we need it? When there is no .bss section, we don't
pad the .data section. */
. = ALIGN(. != 0 ? 64 / 8 : 1);
}
/*********************************************************************************/
.interp : { *(.interp) }
.note.gnu.build-id : { *(.note.gnu.build-id) }
.hash : { *(.hash) }
.gnu.hash : { *(.gnu.hash) }
.dynsym : { *(.dynsym) }
.dynstr : { *(.dynstr) }
.gnu.version : { *(.gnu.version) }
.gnu.version_d : { *(.gnu.version_d) }
.gnu.version_r : { *(.gnu.version_r) }
.rel.init : { *(.rel.init) }
.rela.init : { *(.rela.init) }
.rel.text : { *(.rel.text .rel.text.* .rel.gnu.linkonce.t.*) }
.rela.text : { *(.rela.text .rela.text.* .rela.gnu.linkonce.t.*) }
.rel.fini : { *(.rel.fini) }
.rela.fini : { *(.rela.fini) }
.rel.rodata : { *(.rel.rodata .rel.rodata.* .rel.gnu.linkonce.r.*) }
.rela.rodata : { *(.rela.rodata .rela.rodata.* .rela.gnu.linkonce.r.*) }
.rel.data.rel.ro : { *(.rel.data.rel.ro* .rel.gnu.linkonce.d.rel.ro.*) }
.rela.data.rel.ro : { *(.rela.data.rel.ro* .rela.gnu.linkonce.d.rel.ro.*) }
.rel.data : { *(.rel.data .rel.data.* .rel.gnu.linkonce.d.*) }
.rela.data : { *(.rela.data .rela.data.* .rela.gnu.linkonce.d.*) }
.rel.tdata : { *(.rel.tdata .rel.tdata.* .rel.gnu.linkonce.td.*) }
.rela.tdata : { *(.rela.tdata .rela.tdata.* .rela.gnu.linkonce.td.*) }
.rel.tbss : { *(.rel.tbss .rel.tbss.* .rel.gnu.linkonce.tb.*) }
.rela.tbss : { *(.rela.tbss .rela.tbss.* .rela.gnu.linkonce.tb.*) }
.rel.ctors : { *(.rel.ctors) }
.rela.ctors : { *(.rela.ctors) }
.rel.dtors : { *(.rel.dtors) }
.rela.dtors : { *(.rela.dtors) }
.rel.got : { *(.rel.got) }
.rela.got : { *(.rela.got) }
.rel.bss : { *(.rel.bss .rel.bss.* .rel.gnu.linkonce.b.*) }
.rela.bss : { *(.rela.bss .rela.bss.* .rela.gnu.linkonce.b.*) }
.rel.ldata : { *(.rel.ldata .rel.ldata.* .rel.gnu.linkonce.l.*) }
.rela.ldata : { *(.rela.ldata .rela.ldata.* .rela.gnu.linkonce.l.*) }
.rel.lbss : { *(.rel.lbss .rel.lbss.* .rel.gnu.linkonce.lb.*) }
.rela.lbss : { *(.rela.lbss .rela.lbss.* .rela.gnu.linkonce.lb.*) }
.rel.lrodata : { *(.rel.lrodata .rel.lrodata.* .rel.gnu.linkonce.lr.*) }
.rela.lrodata : { *(.rela.lrodata .rela.lrodata.* .rela.gnu.linkonce.lr.*) }
.rel.plt : { *(.rel.plt) }
.rela.plt : { *(.rela.plt) }
.init :
{
KEEP (*(.init))
} =0x90909090
.plt : { *(.plt) }
.fini :
{
KEEP (*(.fini))
} =0x90909090
PROVIDE (__etext = .);
PROVIDE (_etext = .);
PROVIDE (etext = .);
.eh_frame_hdr : { *(.eh_frame_hdr) }
.eh_frame : ONLY_IF_RO { KEEP (*(.eh_frame)) }
.gcc_except_table : ONLY_IF_RO { *(.gcc_except_table .gcc_except_table.*) }
/* Adjust the address for the data segment. We want to adjust up to
the same address within the page on the next page up. */
. = ALIGN (CONSTANT (MAXPAGESIZE)) - ((CONSTANT (MAXPAGESIZE) - .) & (CONSTANT (MAXPAGESIZE) - 1)); . = DATA_SEGMENT_ALIGN (CONSTANT (MAXPAGESIZE), CONSTANT (COMMONPAGESIZE));
/* Exception handling */
.eh_frame : ONLY_IF_RW { KEEP (*(.eh_frame)) }
.gcc_except_table : ONLY_IF_RW { *(.gcc_except_table .gcc_except_table.*) }
/* Thread Local Storage sections */
.tdata : { *(.tdata .tdata.* .gnu.linkonce.td.*) }
.tbss : { *(.tbss .tbss.* .gnu.linkonce.tb.*) *(.tcommon) }
.preinit_array :
{
PROVIDE_HIDDEN (__preinit_array_start = .);
KEEP (*(.preinit_array))
PROVIDE_HIDDEN (__preinit_array_end = .);
}
.init_array :
{
PROVIDE_HIDDEN (__init_array_start = .);
KEEP (*(SORT(.init_array.*)))
KEEP (*(.init_array))
PROVIDE_HIDDEN (__init_array_end = .);
}
.fini_array :
{
PROVIDE_HIDDEN (__fini_array_start = .);
KEEP (*(.fini_array))
KEEP (*(SORT(.fini_array.*)))
PROVIDE_HIDDEN (__fini_array_end = .);
}
.ctors :
{
/* gcc uses crtbegin.o to find the start of
the constructors, so we make sure it is
first. Because this is a wildcard, it
doesn't matter if the user does not
actually link against crtbegin.o; the
linker won't look for a file to match a
wildcard. The wildcard also means that it
doesn't matter which directory crtbegin.o
is in. */
KEEP (*crtbegin.o(.ctors))
KEEP (*crtbegin?.o(.ctors))
/* We don't want to include the .ctor section from
the crtend.o file until after the sorted ctors.
The .ctor section from the crtend file contains the
end of ctors marker and it must be last */
KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .ctors))
KEEP (*(SORT(.ctors.*)))
KEEP (*(.ctors))
}
.dtors :
{
KEEP (*crtbegin.o(.dtors))
KEEP (*crtbegin?.o(.dtors))
KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .dtors))
KEEP (*(SORT(.dtors.*)))
KEEP (*(.dtors))
}
.jcr : { KEEP (*(.jcr)) }
.data.rel.ro : { *(.data.rel.ro.local* .gnu.linkonce.d.rel.ro.local.*) *(.data.rel.ro* .gnu.linkonce.d.rel.ro.*) }
.dynamic : { *(.dynamic) }
.got : { *(.got) }
. = DATA_SEGMENT_RELRO_END (24, .);
.got.plt : { *(.got.plt) }
/*********************************************************************************/
/* 섹션 재배치로 인해 이동된 부분 */
_edata = .; PROVIDE (edata = .);
/*********************************************************************************/
.lbss :
{
*(.dynlbss)
*(.lbss .lbss.* .gnu.linkonce.lb.*)
*(LARGE_COMMON)
}
. = ALIGN(64 / 8);
.lrodata ALIGN(CONSTANT (MAXPAGESIZE)) + (. & (CONSTANT (MAXPAGESIZE) - 1)) :
{
*(.lrodata .lrodata.* .gnu.linkonce.lr.*)
}
.ldata ALIGN(CONSTANT (MAXPAGESIZE)) + (. & (CONSTANT (MAXPAGESIZE) - 1)) :
{
*(.ldata .ldata.* .gnu.linkonce.l.*)
. = ALIGN(. != 0 ? 64 / 8 : 1);
}
. = ALIGN(64 / 8);
_end = .; PROVIDE (end = .);
. = DATA_SEGMENT_END (.);
/* Stabs debugging sections. */
.stab 0 : { *(.stab) }
.stabstr 0 : { *(.stabstr) }
.stab.excl 0 : { *(.stab.excl) }
.stab.exclstr 0 : { *(.stab.exclstr) }
.stab.index 0 : { *(.stab.index) }
.stab.indexstr 0 : { *(.stab.indexstr) }
.comment 0 : { *(.comment) }
```생략```
}
(3) makefile 생성
- 보호 모드 커널과 달리, IA-32e 모드 커널은 커널 엔트리 포인트와 C언어 커널 엔트리 포인트가 개별적으로 빌드되어 합쳐지는 형태가 아니다.
- IA-32e 모드 커널의 커널 엔트리 포인트 파일은 오브젝트 파일의 형태로 컴파일 되어, C언어 커널과 함께 링크된다.
- IA-32e 모드 커널의 makefile을 작성한다.
- (2.Kernel64)에 makefile을 작성한다.
#####################################
# 빌드 환경 및 규칙 설정
#####################################
NASM64 = nasm -f elf64
GCC64 = gcc -c -m64 -ffreestanding
LD64 = ld -melf_x86_64 -T ../elf_x86_64.x -nostdlib -e Main -Ttext 0x200000
OBJCOPY64 = objcopy -j .text -j .data -j .rodata -j .bss -S -O binary
OBJECTDIRECTORY = Temp
SOURCEDIRECTORY = Source
#####################################
# 빌드 항목 및 빌드 방법 설정
#####################################
all: prepare Kernel64.bin
prepare:
mkdir -p $(OBJECTDIRECTORY)
dep:
@echo === Make Dependancy File ===
make -C $(OBJECTDIRECTORY) -f ../Makefile InternalDependency
@echo === Dependancy Search Complete ===
ExecuteInternalBuild: dep
make -C $(OBJECTDIRECTORY) -f ../Makefile Kernel64.elf
Kernel64.bin: ExecuteInternalBuild
$(OBJCOPY64) $(OBJECTDIRECTORY)/Kernel64.elf $@
clean:
rm -f *.bin
rm -f $(OBJECTDIRECTORY)/*.*
################################################################
# Make에 의해 다시 호출되는 부분, Temp 디렉터리를 기준으로 수행됨
################################################################
# 빌드할 어셈블리어 엔트리 포인트 소스 파일 정의, Temp 디렉터리를 기준으로 설정
ENTRYPOINTSOURCEFILE = ../$(SOURCEDIRECTORY)/EntryPoint.s
ENTRYPOINTOBJECTFILE = EntryPoint.o
# 빌드할 C 소스 파일 정의, Temp 디렉터리를 기준으로 설정
CSOURCEFILES = $(wildcard ../$(SOURCEDIRECTORY)/*.c)
ASSEMBLYSOURCEFILES = $(wildcard ../$(SOURCEDIRECTORY)/*.asm)
COBJECTFILES = $(notdir $(patsubst %.c,%.o,$(CSOURCEFILES)))
ASSEMBLYOBJECTFILES = $(notdir $(patsubst %.asm,%.o,$(ASSEMBLYSOURCEFILES)))
# 어셈블리어 엔트리 포인트 빌드
$(ENTRYPOINTOBJECTFILE): $(ENTRYPOINTSOURCEFILE)
$(NASM64) -o $@ $<
# .c 파일을 .o 파일로 바꾸는 규칙 정의
%.o: ../$(SOURCEDIRECTORY)/%.c
$(GCC64) -c $<
# .asm 파일을 .o 파일로 바꾸는 규칙 정의
%.o: ../$(SOURCEDIRECTORY)/%.asm
$(NASM64) -o $@ $<
# 실제 의존성에 관련된 파일을 생성
InternalDependency:
$(GCC64) -MM $(CSOURCEFILES) > Dependency.dep
# 실제 커널 이미지를 빌드
Kernel64.elf: $(ENTRYPOINTOBJECTFILE) $(COBJECTFILES) $(ASSEMBLYOBJECTFILES)
$(LD64) -o $@ $^
# 현재 디릭토리의 파일 중, dependency 파일이 있으면 make에 포함
ifeq (Dependency.dep, $(wildcard Dependency.dep))
include Dependency.dep
endifⅤ
Ⅴ. 보호 모드 커널과 IA-32e 모드 커널 통합
1. 최상위 makefile 수정
**all: BootLoader Kernel32 Kernel64 Disk.img Utility**
BootLoader:
@echo
@echo =================== Build Boot Loader ===================
@echo
make -C 0.BootLoader
@echo
@echo ==================== Build Complete ====================
@echo
Kernel32:
@echo
@echo =================== Build 32bit Kernel ===================
@echo
make -C 1.Kernel32
@echo
@echo ==================== Build Complete ====================
@echo
**Kernel64:
@echo
@echo =================== Build 64bit Kernel ===================
@echo
make -C 2.Kernel64
@echo
@echo ==================== Build Complete ====================
@echo**
**Disk.img: 0.BootLoader/BootLoader.bin 1.Kernel32/Kernel32.bin 2.Kernel64/Kernel64.bin**
@echo
@echo =================== Disk Image Build Start ===================
@echo
./ImageMaker.exe $^
@echo
@echo ==================== All Build Complete ====================
@echo
Utility:
@echo
@echo ==================== Utility Build Start ====================
@echo
make -C 4.Utility
@echo
@echo ==================== Utility Build Complete ====================
@echo
clean:
make -C 0.BootLoader clean
make -C 1.Kernel32 clean
**make -C 2.Kernel64 clean**
make -C 4.Utility clean
rm -f Disk.img
2. 부트 로더 파일 수정
- 부트 로더 영역에 보호 모드 커널의 섹터 수를 저장하기 위한 공간을 추가한 코드이다.
- (0.BootLoader)에 Bootloader.asm파일을 수정한다.
[ORG 0x00] ; 코드의 시작 어드레스를 0x00으로 설정
[BITS 16] ; 이하의 코드는 16비트 코드로 설정
SECTION .text ; text 섹션(세그먼트 정의)
jmp 0x07C0:START
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; MINT64 OS에 관련된 환경설정 값
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
TOTALSECTORCOUNT: dw 0x02 ;부트로더를 제외한 MINT64 OS 이미지의 크기. 최대 1152 섹터(0x90000byte)까지 가능
KERNEL32SECTORCOUNT: dw 0x02 ;보호 모드 커널의 총 섹터 수
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 코드 영역
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
START:
3. 이미지 메이커 프로그램 수정
- (4.Utility/0.ImageMaker)의 ImageMaker.c 파일을 수정한다.
- gcc -o ImageMaker.exe ImageMaker.c 로 아래 코드를 실행하면, ImageMaker.exe파일이 생성된다. 이를 최상위 디렉터리로 복사하여 새로 생성된 ImageMaker프로그램이 사용되도록 한다.
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/io.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#define BYTESOFSECTOR 512
#define BYTESOFSECTOR 512
#ifndef O_BINARY
#define O_BINARY 0
#endif
#ifndef S_IREAD
#define S_IREAD 0x400
#endif
#ifndef S_IWRITE
#define S_IWRITE 0x200
#endif
// 함수 선언
int AdjustInSectorSize( int iFd, int iSourceSize );
void WriteKernelInformation( int iTargetFd, int iTotalKernelSectorCount, int iKernel32SectorCount );
int CopyFile( int iSourceFd, int iTargetFd );
// Main 함수
int main(int argc, char* argv[])
{
int iSourceFd;
int iTargetFd;
int iBootLoaderSize;
int iKernel32SectorCount;
int iKernel64SectorCount;
int iSourceSize;
// 커맨드 라인 옵션 검사
if( argc < 4 )
{
fprintf( stderr, "[ERROR] ImageMaker.exe BootLoader.bin Kernel32.bin Kernel64.bin\n" );
exit( -1 );
}
// Disk.img 파일을 생성
if( ( iTargetFd = open( "Disk.img", O_RDWR | O_CREAT | O_TRUNC | O_BINARY, S_IREAD | S_IWRITE ) ) == -1 )
{
fprintf( stderr , "[ERROR] Disk.img open fail.\n" );
exit( -1 );
}
//------------------------------------------------------------
// 부트 로더 파일을 열어서 모든 내용을 디스크 이미지 파일로 복사
//------------------------------------------------------------
printf( "[INFO] Copy boot loader to image file\n" );
if( ( iSourceFd = open( argv[ 1 ], O_RDONLY | O_BINARY ) ) == -1 )
{
fprintf( stderr, "[ERROR] %s open fail\n", argv[ 1 ] );
exit( -1 );
}
iSourceSize = CopyFile( iSourceFd, iTargetFd );
close( iSourceFd );
// 파일 크기를 섹터 크기인 512바이트로 맞추기 위해 나머지 부분을 0x00 으로 채움
iBootLoaderSize = AdjustInSectorSize( iTargetFd , iSourceSize );
printf( "[INFO] %s size = [%d] and sector count = [%d]\n",
argv[ 1 ], iSourceSize, iBootLoaderSize );
//-------------------------------------------------------------
// 64비트 커널 파일을 열어서 모든 내용을 디스크 이미지 파일로 복사
//-------------------------------------------------------------
printf( "[INFO] Copy IA-32e mode kernel to image file\n" );
if( ( iSourceFd = open( argv[ 3 ], O_RDONLY | O_BINARY ) ) == -1 )
{
fprintf( stderr, "[ERROR] %s open fail\n", argv[ 3 ] );
exit( -1 );
}
iSourceSize = CopyFile( iSourceFd, iTargetFd );
close( iSourceFd );
// 파일 크기를 섹터 크기인 512바이트로 맞추기 위해 나머지 부분을 0x00 으로 채움
iKernel64SectorCount = AdjustInSectorSize( iTargetFd, iSourceSize );
printf( "[INFO] %s size = [%d] and sector count = [%d]\n",
argv[ 3 ], iSourceSize, iKernel64SectorCount );
//----------------------------------
// 디스크 이미지에 커널 정보를 갱신
//----------------------------------
printf( "[INFO] Start to write kernel information\n" );
// 부트섹터의 5번째 바이트부터 커널에 대한 정보를 넣음
WriteKernelInformation( iTargetFd, iKernel32SectorCount + iKernel64SectorCount, iKernel32SectorCount );
printf( "[INFO] Image file create complete\n" );
close( iTargetFd );
return 0;
}
// 현재 위치부터 512바이트 배수 위치까지 맞추어 0x00으로 채움
int AdjustInSectorSize( int iFd, int iSourceSize )
{
int i;
int iAdjustSizeToSector;
char cCh;
int iSectorCount;
iAdjustSizeToSector = iSourceSize % BYTESOFSECTOR;
cCh = 0x00;
if( iAdjustSizeToSector != 0 )
{
iAdjustSizeToSector = 512 - iAdjustSizeToSector;
printf( "[INFO] File size [%lu] and fill [%u] byte\n", iSourceSize,
iAdjustSizeToSector );
for( i = 0 ; i < iAdjustSizeToSector ; i++ )
{
write( iFd , &cCh , 1 );
}
}
else
{
printf( "[INFO] File size is aligned 512 byte\n" );
}
// 섹터 수를 되돌려줌
iSectorCount = ( iSourceSize + iAdjustSizeToSector ) / BYTESOFSECTOR;
return iSectorCount;
}
// 부트 로더에 커널에 대한 정보를 삽입
void WriteKernelInformation( int iTargetFd, int iTotalKernelSectorCount, int iKernel32SectorCount )
{
unsigned short usData;
long lPosition;
// 파일의 시작에서 5바이트 떨어진 위치가 커널의 총 섹터 수 정보를 나타냄
lPosition = lseek( iTargetFd, 5, SEEK_SET );
if( lPosition == -1 )
{
fprintf( stderr, "lseek fail. Return value = %d, errno = %d, %d\n",
lPosition, errno, SEEK_SET );
exit( -1 );
}
// 부트 로더를 제외한 총 섹터 수 및 보호 모드 커널의 섹터 수 저장
usData = ( unsigned short ) iTotalKernelSectorCount;
write( iTargetFd, &usData, 2 );
usData = ( unsigned short ) iKernel32SectorCount;
write( iTargetFd, &usData, 2 );
printf( "[INFO] Total sector count except boot loader [%d]\n",
iTotalKernelSectorCount );
printf( "[INFO] Total sector count of protected mode kernel [%d]\n",
iKernel32SectorCount );
}
// 소스 파일(Source FD)의 내용을 목표 파일(Target FD)에 복사하고 그 크기를 되돌려줌
int CopyFile( int iSourceFd, int iTargetFd )
{
int iSourceFileSize;
int iRead;
int iWrite;
char vcBuffer[ BYTESOFSECTOR ];
iSourceFileSize = 0;
while( 1 )
{
iRead = read( iSourceFd, vcBuffer, sizeof( vcBuffer ) );
iWrite = write( iTargetFd, vcBuffer, iRead );
if( iRead != iWrite )
{
fprintf( stderr, "[ERROR] iRead != iWrite.. \n" );
exit(-1);
}
iSourceFileSize += iRead;
if( iRead != sizeof( vcBuffer ) )
{
break;
}
}
return iSourceFileSize;
}
4. 보호 모드 커널의 C언어 엔트리 포인트 파일 수정
- (1.Kernel32/Source)의 Main.c 소스코드를 수정한다.
#include "Types.h"
#include "Page.h"
#include "ModeSwitch.h"
void kPrintString (int iX, int iY, const char* pcString);
BOOL kInitializeKernel64Area(void);
BOOL kIsMemoryEnough(void);
void kCopyKernel64ImageTo2Mbyte(void);
//Main 함수
void Main(void){
DWORD i;
DWORD dwEAX, dwEBX, dwECX, dwEDX;
char vcVendorString[13] = {0, };
kPrintString( 0, 3, "Protected Mode C Language Kernel Start......[Pass]");
// 최소 메모리 크기를 만족하는지 검사
kPrintString(0, 4, "Minimum Memory Size Chek....................[ ]");
if(kIsMemoryEnough() == FALSE){
kPrintString(45, 4, "Fail");
kPrintString(0, 5, "Not Enough Memory~!! MINT64 OS Requires Over " "64Mbyte Memory~!!");
while(1);
}else{
kPrintString(45, 4, "Pass");
}
// IA-32e 모드의 커널 영역을 초기화
kPrintString(0, 5, "IA-32e Kernel Area Initialization...........[ ]");
if(kInitializeKernel64Area() == FALSE){
kPrintString(45, 5, "Fail");
kPrintString(0, 6, "Kernel Area Initialization Fail~!!");
while(1);
}
kPrintString(45, 5, "Pass");
// IA-32e 모드 커널을 위한 페이지 테이블 생성
kPrintString(0, 6, "IA-32e Page Tables Initialization...........[ ]");
kInitializePageTables();
kPrintString(45, 6, "Pass");
// 프로세서 제조사 정보 읽기
kReadCPUID(0x00, &dwEAX, &dwEBX, &dwECX, &dwEDX) ;
*( DWORD* ) vcVendorString = dwEBX;
*(( DWORD* ) vcVendorString + 1 ) = dwEDX;
*(( DWORD* ) vcVendorString + 2 ) = dwECX;
kPrintString(0, 7, "Processor Vendor String.....................[ ]");
kPrintString(45, 7, vcVendorString );
// 64비트 지원 유무 확인
kReadCPUID(0x80000001, &dwEAX, &dwEBX, &dwECX, &dwEDX);
kPrintString(0, 8, "64bit Mode Support Check....................[ ]");
if(dwEDX & (1 << 29))
{
kPrintString(45, 8, "Pass");
}
else
{
kPrintString(45, 8, "Fail");
kPrintString(0, 9, "This processor does not support 64bit mode~!!");
while(1);
}
// IA-32e 모드 커널을 0x200000(2Mbyte) 어드레스로 이동
kPrintString(0, 9, "Copy IA_32e Kernel To 2M Address............[ ]");
kCopyKernel64ImageTo2Mbyte();
kPrintString(45, 9, "Pass");
// IA-32e 모드로 전환
kPrintString(0, 10, "Switch To IA-32e Mode");
kSwitchAndExecute64bitKernel();
while(1) ;
}
//문자열 출력 함수
void kPrintString(int iX, int iY, const char* pcString){
``` 생략 ```
}
// IA-32e 모드용 커널 영역을 0으로 초기화
BOOL kInitializeKernel64Area( void )
{
``` 생략 ```
}
//MINT64 OS를 실행하기에 충분한 메모리를 가지고 있는지 체크
BOOL kIsMemoryEnough( void )
{
``` 생략 ```
}
// IA-32e 모드 커널을 0x200000(2Mbyte) 어드레스에 복사
void kCopyKernel64ImageTo2Mbyte(void){
WORD wKernel32SectorCount, wTotalKernelSectorCount;
DWORD* pdwSourceAddress,* pdwDestinationAddress;
int i;
// 0x7C05에 총 커널 섹터 수, 0x7C07에 보호 모드 커널 섹터 수가 들어 있음
wTotalKernelSectorCount = *((WORD*) 0x7C05);
wKernel32SectorCount = *((WORD*) 0x7C07);
pdwSourceAddress = (DWORD*) (0x10000 + (wKernel32SectorCount * 512));
pdwDestinationAddress = (DWORD*) 0x200000;
// IA-32e 모드 커널 섹터 크기만큼 복사
for(i=0; i<512 * (wTotalKernelSectorCount - wKernel32SectorCount)/4; i++){
*pdwDestinationAddress = *pdwSourceAddress;
pdwDestinationAddress++;
pdwSourceAddress++;
}
}
'MINT64 OS 개발' 카테고리의 다른 글
12장. GDT, IDT 테이블, TSS 세그먼트를 추가해 인터럽트에 대비하자 (0) | 2023.07.21 |
---|---|
11장. 키보드 디바이스 드라이버 추가 (0) | 2023.07.20 |
9장. 페이징 기능을 활성화하여, 64비트 전환 준비 (0) | 2023.07.20 |
8장. A20 게이트를 활성화하여 1MB이상 영역에 접근 (0) | 2023.07.20 |
7장. C언어로 커널 작성 (0) | 2023.07.20 |