CPUID
명령어를 이용하여, 프로세서 제조사를 확인하는 방법과 IA-32e 모드를 지원하는지 확인하는 방법에 대해 살펴본다.스택에 들어있는 2번째 파라미터부터 5번째 파라미터까지는 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 모드 지원 여부를 검사하는 코드
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 모드의 서브 모드 중, 64bit 모드를 사용할 것이므로 보호 모드용 세그먼트 디스크립터를 기반으로 L비트를 1로 D비트를 0으로 설정하면 된다.
D비트를 0으로 설정하는 이유는 L비트와 D비트가 모두 1인 경우를 다른 목적으로 예약해두었기 때문이다.
(실제 PC의 경우, L비트와 D비트를 모두 1로 설정하면 리부팅할 수 있으니 주의해야한다.)
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:
세그먼트 디스크립터 오프셋 변경
[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 언어 커널 수행Ⅲ
; 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)를 저장