Operating Systems : simple Operating System from scratch (7) — Exception ( 1 )

吳建興
9 min readMay 26, 2021

--

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 IDTR
lidt 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_int
ignore_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
iretq
int_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

結語

簡單的例外處理結束了!下一篇希望能來介紹怎麼讓不同的例外處理,顯示不同的字串!

Reference

  1. store itdr on x64
  2. 一個64位操作系統的設計與實現
  3. Task State Segment
  4. 分段架構
  5. "Present" flag
  6. Data Segment Descriptor
  7. segment descriptor v.s. gate descriptor
  8. x86 descriptor

--

--

吳建興
吳建興

Written by 吳建興

I want to be a good programmer.(´・ω・`)

No responses yet