# Setting up long mode 基於 david942j 大大的建議,做完 real mode 先閱讀一下 osdev.wiki 的 [setting up long mode](https://wiki.osdev.org/Setting_Up_Long_Mode) ## Introduction x86_64 的出現引進了新的 mode: long mode , long mode 可以再分為兩個 submode: 32bit, 64bit: - 64bit: 原先的 eax, ebx......等等擴展成 64bit ,另外增加了 general registers: r8 ~ r15 ,GDT, IDT, paging 都跟 32bit 不同 - 32bit: 在 long mode 下稱為相容模式 > 我猜 32bit mode 應該就是所謂的 Protected mode ? ## Detect long mode 以下僅為我的閱讀筆記,尚未寫過 code ,希望沒出什麼錯QQ 簡述之,要檢測 long mode 必須透過 extend function ,而 extent function 則需透過 cpuid 這個指令來確認能否使用,因此關係圖是: detect cpuid -> detect extend function -> long mode 1. detect cpuid: - 可以由 flags register 的 ID flag 可否反轉得知 - 簡單講就是保存 flags ,再對 ID flag 做反轉,跟剛剛保存的做比對,確認不一樣即代表 cpuid 是可用的 ```asm= pushfd pop eax mov ecx, eax ;把原先的 flag 保存在 ecx 中 xor eax, 1 << 21 ;翻轉 id flag push eax popfd ;放回 reg pushfd pop eax ;再給eax push ecx popfd ;存回原先的 flags xor ecx, eax jz .NoCPUID ;比較 + 判斷 ret ``` 2. extent function - 這比較我看不是很懂...直接看 code 好了 ```asm= mov eax, 0x80000000 cpuid ;所以說經過 cpuid 應該要大於 0x80000001 ? cmp eax, 0x80000001 jb .NoLongMode ``` 3. long mode - 透過 cpuid ,判斷 edx 第 29 bit 是否被設為 1 來確定有無 long mode ```x86asm= mov eax, 0x80000001 cpuid test edx, 1 << 29 jz .NoLongMode ``` ## Setting up the page 原本的文章還有關於如何關掉 protected mode 的 paging ,不過我之前沒有進入 protected mode 所以省略 > 有想過進入 long mode 前是否一定要先從 real mode 進入 protected mode ,但是從後面的文章來看應該是不用......吧 long mode 的 paging 模式是用 PAE (Physical Address Extension) 來擴展,具體會是 4 level 的 paging: P4LMT -> PDPT(Page Directory Pointer Table) -> PDT(Page Directory Table) -> PT(Page Table) long mode 為 64bit 的 address 但實際最多只能用 48bit (6 bytes) ,而一個 page 為 4k ,我們的 Page transcation 都是由 virtual address 轉換而來: offset: 12bits (4k == 2 ^ 12) 48 - 12 = 36 36分給4個table,每個可得 9bits ,換句話說,每個 table 最多有 512 個 entries (2^9) ![](https://i.imgur.com/SNUx9uk.png) > 這邊不太清楚的地方是那麼多的 table 究竟是真實存在還是必要時才存在,否則 memory 也存太多 table 了吧... > 基於 virtual 轉成 physical 是縮小範圍,應該不是真的存在那麼多空間 接下來打算讓 PT 為 0x200000 大小,起始地址為 0x1000 ,下一個 0x1000 空間為下一層的 table 空間,而建立 table 需要清掉每個 bytes (以下為 P4LMT): ```asm= mov edi, 0x1000 ;把位在 memory 的 0x1000 index (=base address) 傳給 edi mov cr3, edi ;把基底地址傳給 cr3 (special reg) xor eax, eax mov ecx, 4096 rep stosd ;重複清空該空間 mov edi, cr3 ;回存到 edi ``` 接著設定每層的 table ```x86asm= mov [edi], 0x2003 ; P4LMT 第一層要存 PDPT 起始地址,前幾個 bits 當 flag 用 add edi, 0x1000 ; 一個 table 為 0x1000 大小 mov [edi], 0x3003 ; PDPT 第一層要存 PDT 起始地址,其餘同上 add edi, 0x1000 mov [edi], 0x4003 add edi, 0x1000; PT ``` 設置完所有 table 後,要設定 physical memory ,用 loop 來設定每個 entry ```x86asm= mov ebx, 0x00000003; 同樣前幾個 bits 當 flag 用 mov ecx, 512 ; 有 512 個 entries 要設定 .SetEntry: mov [edi], ebx add ebx, 0x1000 ; 下一個 page add edi, 0x8 ; 下一個 entry loop .SetEntry ``` 聲明將用 PAE 來 paging ,聲明方法是用 cr4 中的 PAE bit: ```x86asm= mov eax, cr4 or eax, 1 << 5 mov cr4, eax ``` 推測無法直接對 cr 系列的 reg 操作,所以需要傳給 general reg 操作完再傳回去 > 後面有提到關於 5 level paging ,不過跟之後的實作無關所以就不紀錄 因為沒進過 protected mode ,所以採用直接由 real mode 進入 long mode 的方法: 1. 設定 EFER MSR 來聲明要用 long mode ```x86= mov ecx, 0xc0000080 rdmsr or eax, 1 << 8 wrmsr ``` 2. 啟用 paging 和 protected mode ```x86= mov eax, cr0 or eax, 1 << 31 | 1 << 0 mov cr0, eax ``` **注意!** 此時只是在 compatible mode ,還需進入 long mode 才是真正進入 64bit 的世界 ## Enter long mode 操作很簡單, load 一個帶有 64 flag 的 GDT即可,具體可以參考 amd64 architecture programming manual vol. 2 ```x86= GDT64: ; Global Descriptor Table (64-bit). .Null: equ $ - GDT64 ; The null descriptor. dw 0xFFFF ; Limit (low). dw 0 ; Base (low). db 0 ; Base (middle) db 0 ; Access. db 1 ; Granularity. db 0 ; Base (high). .Code: equ $ - GDT64 ; The code descriptor. dw 0 ; Limit (low). dw 0 ; Base (low). db 0 ; Base (middle) db 10011010b ; Access (exec/read). db 10101111b ; Granularity, 64 bits flag, limit19:16. db 0 ; Base (high). .Data: equ $ - GDT64 ; The data descriptor. dw 0 ; Limit (low). dw 0 ; Base (low). db 0 ; Base (middle) db 10010010b ; Access (read/write). db 00000000b ; Granularity. db 0 ; Base (high). .Pointer: ; The GDT-pointer. dw $ - GDT64 - 1 ; Limit. dq GDT64 ; Base. ``` 設定完 table ,再來 long gdt 即可 ```x86= lgdt [GDT64.Pointer] jmp GDT64.Code:Realm64 ``` 剩下是後面的 sample ,操作效果是刷新螢幕: ```x86= ; Use 64-bit. [BITS 64] Realm64: cli ; Clear the interrupt flag. mov ax, GDT64.Data ; Set the A-register to the data descriptor. mov ds, ax ; Set the data segment to the A-register. mov es, ax ; Set the extra segment to the A-register. mov fs, ax ; Set the F-segment to the A-register. mov gs, ax ; Set the G-segment to the A-register. mov ss, ax ; Set the stack segment to the A-register. mov edi, 0xB8000 ; Set the destination index to 0xB8000. mov rax, 0x1F201F201F201F20 ; Set the A-register to 0x1F201F201F201F20. mov ecx, 500 ; Set the C-register to 500. rep stosq ; Clear the screen. hlt ; Halt the processor. ``` 因為目前尚未設定 IDT ,所以不能 enable interrupt 這筆記大概到這邊為止