8. 64비트 모드로 전환하자

들어가기

64비트 모드로 전환하는데 필요한 페이징 기능 활성화와 IA-32e 모드 커널을 작성

본론

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

  • 프로세서마다 지원하는 기능이 조금씩 다름.
  • 프로세서 제조사는 지원하는 기능을 확인할 수 있는 CPUID 명령어 제공.

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

  • CPUID 명령어는 EAX에 설정된 값에 따라 해당 정보를 조회, 반환값은 EAX, EBX, ECX, EDX에 저장.

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

[BITS 32]
global kReadCPUID

SECTION .text       ; text 섹션(세그먼트)을 정의

; CPUID를 반환
;   PARAM: DWORD dwEAX, DWORD* pdwEAX,* pdwEBX,* pdwECX,* pdwEDX
kReadCPUID:
    push ebp        ; 베이스 포인터 레지스터(BP)를 스택에 삽입
    mov ebp, esp    ; 베이스 포인터 레지스터(BP)에 스택 포인터 레지스터(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     ; 베이스 포인터 레지스터(BP) 복원
    ret         ; 함수를 호출한 다음 코드의 위치로 복귀
  • CPUID 명령어를 사용하려면 어셈블리어 코드로 직접 레지스터를 제어해야 함.
  • CPUID 명령을 수행하는 함수가 외부에서도 실행 가능하도록 global 사용.
  • 주의해서 볼 부분은 결과를 넘겨줄때 esi를 사용 -> C 코드에서 넘어올 때 주소로 넘어옴.
   // 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 ) ;
    }
  • EAX에 0x80000001을 넣은후 kReadCPUID 호출.

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

8.2.1 보호 모드 커널 엔트리 포인트에 디스크립터 추가

  • 보호 모드의 세그먼트 디스크립터와 거의 같은 크기와 필드로 구성.
  • 차이점, IA-32e 모드에서는 디스크립터의 기준 주소와 세그먼트 크기 값에 상관없이 64GB 전체 영역으로 설정된다는 것, L 비트가 IA-32e 서브 모드 중 호환 모드 또는 64비트 모드를 선택.
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]
  • 편의상 기존 GDT의 NULL 디스크립터 바로 다음에 위치.
    • 이에 따른 보호모드의 CS 세그먼트 변경.

8.3 IA-32e 모드 전환과 1차 정리

8.3.1 물리 메모리 확장 기능 활성화와 페이지 테이블 설정

   ; CR4 컨트롤러 레지스터의 PAE 비트를 1로 설정
    mov eax, cr4    ; CR4 컨트롤 레지스터의 값을 EAX 레지스터에 저장
    or eax, 0x20    ; PAE 비트(비트 5)
    mov cr4, eax    ; 설정된 값을 CR4 컨트롤 레지스터에 저장

    mov eax, 0x100000   ; EAX 레지스터에 PML4 테이블이 존재하는 0x100000(1MB)를 저장
    mov cr3, eax
  • PAE 비트를 1로 설정해서 물리 메모리 확장 기능 사용.
  • cr3 레지스터에 PML4 테이블의 어드레스를 저장.

8.3.2 64비트 모드 활성화와 페이징 활성화

  • IA-32e 모드를 활성화하는 실질적인 최종 관문 IA32_EFER 레지스터의 LME 비트를 1로 설정.
  • IA32_EFER 레지스터를 세팅하지 않으면 IA-32 모드용 세그먼트 레지스터로 교체한다 해도 보호 모드로 동작.
  • 해당 레지스터는 RDMSR 또는 WRMSR로 읽기 및 쓰기.
  • ECX에 해당 레지스터의 주소를 넣으면 결과 상위 32비트는 EDX, 하위 32비트는 EAX에 저장.
  • MSR 중 IA32_EFER은 0xC0000080에 위치.
   ; IA-32EFER.LME를 설정하여 모드 전환
    mov ecx, 0xC0000080 ; IA32_EFER MSR 레지스터의 어드레스를 저장
    rdmsr               ; MSR 레지스터를 읽기

    or eax, 0x0100      ; EAX 레지스터에 저장된 IA32_EFER MSR의 하위 32비트에서 
                        ; LME 비트(비트 8)를 1로 설정
    wrmsr               ; MSR 레지스터에 쓰기
  • LME비트(비트 8)를 1로 설정하여 IA-32e 모드 활성화.

8.3.3 IA-32e 모드로 전환과 세그먼트 셀렉터 초기화

   ; cr0의 NW, CD, PG를 설정
    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            ; 설정한 값을 다시 CR0 컨트롤 레지스터에 저장

    jmp 0x08:0x200000       ; CS 세그먼트 셀렉터를 IA-32e 모드용 코드 세그먼트 디스크립터로
                            ; 교체하고 0x200000(2MB) 어드레스로 이동
  • 앞 정리에서 본 PCD 비트와 PWT 비트는 페이징을 활성화했을 때만 유효.
  • x86 계열의 프로세서에는 페이지의 캐시 설정보다 우선시되는 캐시 비트가 있음.
    • NW && CD
  • 두 비트를 모두 0으로 설정해야 페이지 캐시 사용 가능.

8.3.4 소스코드 1차 정리와 실행

  • 제조사 및 사용여부 확인.

8.4 IA-32e 모드용 커널 준비

8.4.1 커널 엔트리 포인트 파일 생성

[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 $
  • IA-32e 모드 커널 엔트리 포인트의 역할은 보호 모드의 엔트리 포인트와 비슷.
  • 차이점이라면 보호 모드 커널에 IA-32e 모드로 전환하는 모든 코드가 포함되여 IA-32e 모드의 커널 엔트리 포인트는 단순히 세그먼트 레지스터를 교체하고 C 커널 호출.
  • extern 지시어로 외부 Main 함수 사용 가능.

C 언어 엔트리 포인트 파일 생성

  • C 언어 엔트리 포인트는 보호 모드를 그래도 복사해도 상관없이 작동.

링크 스크립트 파일 생성

링커 스크립트 파일 코드 보기

  • IA-32e 커널 역시 보호 모드 커널과 마찬가지로 라이브러리를 사용하지 않도록 빌드.
  • 또 0x200000 어드레스로 복사되어 실행 될 것.

makefile 생성

IA-32e makefile 코드 보기

  • 커널 엔트리 포인트와 C 언어 커널 엔트리 포인트가 개별적으로 빌드되어 합쳐지는 형태가 아님.
  • IA-32e 모드 커널의 커널 엔트리 포인트 파일은 오브젝트 파일의 형태로 컴파일 되어 C 언어 커널과 함께 링크.
  • 따라서 보호 모드 기반으로 사용하되, 커널 엔트리 포인트 파일이 링크 목록의 가장 앞에 위치.

8.5 보호 모드 커널과 IA-32e 모드 커널 통합

8.5.1 최상위 makefile 수정

  • 최상위 makefile에 Kernel64를 추가.

8.5.2 부트 로더 파일 수정

  • 보호 모드 커널의 총 섹터수를 입력.

8.5.3 이미지 메이커 프로그램 수정

이미지 메이커 코드 보기

  • 이미지 메이커의 매개 변수의 개수 변경.
  • 총 섹터 수와 보호 모드 커널 섹터수를 같이 기록하게 변경.

8.5.4 보호 모드 커널의 C 언어 엔트리 포인트 파일 수정

Main() 함수 수정

코드 보기

8.5.5 빌드와 실행

마치며

끝!

Share