Operating Systems : simple Operating System from scratch (7) — Exception ( 1 )
Hello!
上一篇讓我們有了可以在螢幕上顯示字元的能力,這時候可以用這個能力,讓我們知道當我們出錯的時候,是出了哪種錯。
因為目前我們還在特權級 0 裡打滾,不會牽涉到特權轉移時的 stack 轉換,所以還不會對 TSS 進行初始化,並且在這裡僅介紹特權級 0 時的例外處理。而不同特權級間 stack 的轉換,會等到之後來做個筆記 XD。
P.S. 以下描述都是以 x86-64, Intel, Long Mode 為基準。
簡單的例外處理
完整的程式碼請參考 這裡。
載入 idtr :
可以看到, IDTR 的基底位址存著 IDT 的基底物理位址,而長度則是這張表的總 Bytes 數 - 1。
因為 IDT 每個 entry ( gate descriptor, 門描述符 ) 佔了 16 bytes,所以 IDTR 的長度為 16 * N — 1,N 代表 entry 個數。
// file : head.S
//======= load IDTRlidt IDT_POINTER(%rip)mov $0x10, %ax
mov %ax, %ds
mov %ax, %es
mov %ax, %fs
mov %ax, %gs
mov %ax, %ss
.
.
.
.
//======= IDT_Table.globl IDT_TableIDT_Table:
.fill 512,8,0
IDT_END:IDT_POINTER:
IDT_LIMIT: .word IDT_END - IDT_Table - 1
IDT_BASE: .quad IDT_Table
初始化 IDT:
一開始可以看到在程式碼裡,會讓 IDT 全部都是 0。512, 8, 0 各代表 "512" 個 "8" bytes 都設為 "0"。
// file : head.S
//======= IDT_Table.globl IDT_TableIDT_Table:
.fill 512,8,0
IDT_END:IDT_POINTER:
IDT_LIMIT: .word IDT_END - IDT_Table - 1
IDT_BASE: .quad IDT_Table
接下來會有一段程式碼,會將所有在 IDT 內的 gate descriptor 通通都填上同樣的值。
setup_IDT:
leaq ignore_int(%rip), %rdx
movq $(0x08 << 16), %rax
movw %dx, %ax
movq $(0x8E00 << 32), %rcx
addq %rcx, %rax
movl %edx, %ecx
shrl $16, %ecx
shlq $48, %rcx
addq %rcx, %rax
shrq $32, %rdx
leaq IDT_Table(%rip), %rdi
mov $256, %rcx
首先來看 setup_IDT 到 rp_sidt 間的部份,這邊是將 rax 初始化成一個 gate descriptor 的前 8 個 bytres,並將 rdx 初始化成一個 gate descriptor 的後 8 個 bytes 。
經過一連串的計算之後,值如下所示:
rax :
rdx :
所以 gate descriptor 是 :
segment selector : 段選擇子
offset : 段內偏移
P : Present, 1 ==> 在記憶體內, 0 ==> 不在記憶體內
DPL : 特權級, 0 為最高的特權級。
S: 1 --> 非系統段描述符。 0 --> 系統段描述符
Type : 段的種類
e.g. b1001 --> x86-64 下的 TSS 段描述符
e.g. b1110 --> x86–64 下的中斷門描述符
IST ( Interrupt Stack Table) : IST 機制提供了 7 個 stack pointer 可使用,若 IST == 0 , 則不使用此機制。
rp_sidt:
movq %rax, (%rdi)
movq %rdx, 8(%rdi)
addq $0x10, %rdi
dec %rcx
jne rp_sidt
而這一段則是連續將 16 bytes 賦值給 IDT 256 次,所以 IDT 內全部 256 個 gate descriptor 都是相同的。
ignore_int 的內容就是把所有的暫存器 ( register ) 存起來,並用 color_printk 這個 function 顯示 "Unknown interrupt or fault at RIP" 字樣。
// file : head.S
//======= ignore_intignore_int:
cld
pushq %rax
pushq %rbx
pushq %rcx
pushq %rdx
pushq %rbp
pushq %rdi
pushq %rsi
pushq %r8
pushq %r9
pushq %r10
pushq %r11
pushq %r12
pushq %r13
pushq %r14
pushq %r15
movq %es, %rax
pushq %rax
movq %ds, %rax
pushq %rax
movq $0x10, %rax
movq %rax, %ds
movq %rax, %es
leaq int_msg(%rip), %rax /* leaq get address */
pushq %rax
movq %rax, %rdx
movq $0x00000000, %rsi
movq $0x00ff0000, %rdi
movq $0, %rax
callq color_printk
addq $0x8, %rsp
Loop:
jmp Loop
popq %rax
movq %rax, %ds
popq %rax
movq %rax, %es
popq %r15
popq %r14
popq %r13
popq %r12
popq %r11
popq %r10
popq %r9
popq %r8
popq %rsi
popq %rdi
popq %rbp
popq %rdx
popq %rcx
popq %rbx
popq %rax
iretqint_msg:
.asciz "Unknown interrupt or fault at RIP\n"
接著故意在 main function 裡面塞進一個 1/0 , 這時後會觸發 #DE "除法錯誤",看看我們的例外處理機制有沒有正常的運作。
// file : main
.
.
.
.
/* 初始化 FrameBuffer 的起始 "虛擬位址" */
Pos.FB_addr = (int *)0xffff800003000000;
/* FrameBuffer 的長度 ... 解析度 * 4 bytes ( 一個 pixel 用 4 bytes 表示 ) 並對齊一個 page 的大小 */
Pos.FB_length = (Pos.XResolution * Pos.YResolution * 4 + PAGE_4K_SIZE - 1) & PAGE_4K_MASK; color_printk(WHITE, BLACK,"Hello World %s!\n", "fdgkhdkgh"); int a = 1 / 0; while(1)
;
這邊可以看到截圖上有出現我們想要顯示的字串,這表示我們的例外處理機制算是有正常的運作中,太好了。
指向一個區間 v.s. 指向一個點 :
這邊依據欄位的屬性,將其分為指向一個區間,或是指向一個點。
指向一個區間 : 有段的基底位址,部份還包含了段的長度。
指向一個點 : 有一個段選擇子,會到 GDT 或 LDT 選擇一個段描述符。此外還有一個段內偏移。
簡而言之,
"指向一個區間"很簡單的就是描述一個段 ( segment ) 的資訊,一個段在哪?一個段多長?( 雖然在 long mode 下,資料段與程式碼段的長度欄位並無意義 ),意指一個段 ( 一個區間 )。
"指向一個點" 則包含了 段選擇子,由段選擇子我們可以找到一個段描述符。段描述符 + 段內偏移 = 線性位址,意指一個位址 ( 一個點 )。
“指向一個區間”的例子:
Code Segment Descriptor ( 程式碼段描述符 ):
非系統段, 's' flag == 1
'不'包含段的長度
共 64 bits
Data Segment Descriptor ( 資料段描述符 ) :
非系統段, 's' flag == 1
‘不’包含段的長度
共 64 bits
Task State Segment Descriptor :
系統段, 's' flag == 0
包含段的長度
共 128 bits
“指向一個點”的例子:
Call Gate Descriptor ( 調用門描述符 ) :
系統段, ‘s’ flag == 0
可讓不同特權級間的程式碼進行受控的跳轉
共 128 bits
Interrupt Gate Descriptor ( 中斷門描述符 ) :
系統段, ‘s’ flag == 0
中斷門描述符會暫時屏蔽中斷 ( 將 EFLAGS 的 IF 設為 0 )
共 128 bits
Trap Gate Descriptor ( 陷阱門描述符 ) :
系統段, ‘s’ flag == 0
陷阱門描述符不會暫時屏蔽中斷
共 128 bits
結語
簡單的例外處理結束了!下一篇希望能來介紹怎麼讓不同的例外處理,顯示不同的字串!