0177-長模式檢查

波尔發表於2024-07-14

環境

  • Time 2022-11-12
  • WSL-Ubuntu 22.04
  • QEMU 6.2.0
  • NASM 2.15.05

前言

說明

參考:https://os.phil-opp.com/entering-longmode//

目標

定義一個長模式檢查函式,驗證 CPU 是否支援長模式。
長模式也就是 64 位模式。

定義棧

需要先定義棧資訊,後面的檢查需要使用棧。

section .bss
stack_bottom:
    resb 64
stack_top:

檢查 CPUID

check_cpuid:
    ; 檢查 CPUID 是否支援可以透過翻轉 ID 位,即第 21 位。
    ; 如果在 FLAGS 標誌暫存器中,我們能夠翻轉它,CPUID 就是可用的。

    ; 透過棧複製 FLAGS 暫存器的值到 EAX 暫存器
    pushfd
    pop eax

    ; 將 EAX 的值複製到 ECX,後面要用
    mov ecx, eax

    ; 翻轉第 21 位
    xor eax, 1 << 21

    ; 把 EAX 的值複製回 FLAGS 暫存器
    push eax
    popfd

    ; 複製 FLAGS 暫存器的值回 EAX 暫存器,檢查是否翻轉成功,成功翻轉則支援 CPUID
    pushfd
    pop eax

    ; 透過 ECX 還原 EFLAGS 中的值
    push ecx
    popfd

    ; 比較,如果兩個一樣,則翻轉不成功,不支援CPUID;如果翻轉成功,則支援CPUID
    cmp eax, ecx
    je .no_cpuid
    ret
.no_cpuid:
    mov al, "1"
    jmp error

檢查長模式

check_long_mode:

    ; 檢查是否有擴充套件的處理器資訊可用
    mov eax, 0x80000000    ; CPUID 的隱式引數
    cpuid                  ; 獲取最高支援的引數
    cmp eax, 0x80000001    ; 如果支援長模式,至少是 0x80000001
    jb .no_long_mode       ; 如果小於,則不支援長模式

    ; 使用擴充套件資訊驗證是否支援長模式
    mov eax, 0x80000001    ; 擴充套件處理器引數資訊
    cpuid                  ; 將各種特徵標記位返回到 ECX 和 EDX
    test edx, 1 << 29      ; 第 29 位是 long mode 長模式標記位,檢查是否支援
    jz .no_long_mode       ; 如果為 0,表示不支援長模式
    ret
.no_long_mode:
    mov al, "2"
    jmp error

主邏輯

global start
section .text
bits 32
start:

; 棧是否高地址往低地址增長
mov esp, stack_top

call check_cpuid
call check_long_mode

總結

透過對 CPUID 和長模式的檢查,確認能夠進入 64 位模式。

附錄

原始碼

section .multiboot_header
header_start:
    dd 0x1BADB002  ; 魔法數字,固定值
    dd 0
    dd -0x1BADB002 ; 定義的這三個數字相加需要等於0
header_end:

global start
section .text
bits 32
start:

; 棧是否高地址往低地址增長
mov esp, stack_top

call check_cpuid
call check_long_mode

check_cpuid:
    ; 檢查 CPUID 是否支援可以透過翻轉 ID 位,即第 21 位。
    ; 如果在 FLAGS 標誌暫存器中,我們能夠翻轉它,CPUID 就是可用的。

    ; 透過棧複製 FLAGS 暫存器的值到 EAX 暫存器
    pushfd
    pop eax

    ; 將 EAX 的值複製到 ECX,後面要用
    mov ecx, eax

    ; 翻轉第 21 位
    xor eax, 1 << 21

    ; 把 EAX 的值複製回 FLAGS 暫存器
    push eax
    popfd

    ; 複製 FLAGS 暫存器的值回 EAX 暫存器,檢查是否翻轉成功,成功翻轉則支援 CPUID
    pushfd
    pop eax

    ; 透過 ECX 還原 EFLAGS 中的值
    push ecx
    popfd

    ; 比較,如果兩個一樣,則翻轉不成功,不支援CPUID;如果翻轉成功,則支援CPUID
    cmp eax, ecx
    je .no_cpuid
    ret
.no_cpuid:
    mov al, "1"
    jmp error

check_long_mode:

    ; 檢查是否有擴充套件的處理器資訊可用
    mov eax, 0x80000000    ; CPUID 的隱式引數
    cpuid                  ; 獲取最高支援的引數
    cmp eax, 0x80000001    ; 如果支援長模式,至少是 0x80000001
    jb .no_long_mode       ; 如果小於,則不支援長模式

    ; 使用擴充套件資訊驗證是否支援長模式
    mov eax, 0x80000001    ; 擴充套件處理器引數資訊
    cpuid                  ; 將各種特徵標記位返回到 ECX 和 EDX
    test edx, 1 << 29      ; 第 29 位是 long mode 長模式標記位,檢查是否支援
    jz .no_long_mode       ; 如果為 0,表示不支援長模式
    ret
.no_long_mode:
    mov al, "2"
    jmp error

; 列印 `ERR: ` 和一個錯誤程式碼並停住。
; 錯誤程式碼在 al 暫存器中
error:
    mov dword [0xb8000], 0x4f524f45
    mov dword [0xb8004], 0x4f3a4f52
    mov dword [0xb8008], 0x4f204f20
    mov byte  [0xb800a], al
    hlt

section .bss
stack_bottom:
    resb 64
stack_top:

相關文章