第11章
進入保護模式;初始化全局描述符表,通過GDT進入代碼段、數據段、堆棧段
; FILE: c11_mbr.asm
; DATE: 20191229
; TITLE: 硬盤主引導扇區代碼
; 設置堆棧段和棧指針
; 0x07c00以此爲界限,mbr代碼段cs:ip向上,mbr堆棧段ss:sp向下
mov ax, cs
mov ss, ax
mov sp, 0x7c00
; 計算gdt所在的邏輯段地址
; 32位忽略高位0,折算爲20位,即ds:bx形式(ds*16 + bx)
mov ax, [cs:gdt_base + 0x7c00] ; 低16位
mov dx, [cs:gdt_base + 0x7c00 + 2] ; 高16位
mov bx, 16
div bx
mov ds, ax ; 商爲邏輯段地址
mov bx, dx ; 餘數爲偏移地址
; 創建gdt第#0號描述符
; 處理器規定,gdt中第一個描述符必須是空描述符
mov dword [bx], 0x00000000
mov dword [bx+0x04], 0
; 創建gdt第#1號描述符,保護模式下的代碼段描述符
; 該段:線性基地址爲0x0000 7c00,段界限爲0x0 01FF(即段長512字節),粒度爲字節(G=0)
; 屬於存儲器的段(S=1),是32位的段(D=1),目前位於內存中(P=1),段的特權級爲0(DPL=00),
; 是可執行的代碼段(TYPE=1000)
mov dword [bx+0x08],0x7c0001ff
mov dword [bx+0x0c],0x00409800
; 創建gdt第#2號描述符,保護模式下的數據段描述符(文本模式下的顯示緩衝區)
; 該段:線性基地址爲0x0000 b800,段界限爲0x0 FFFF(即段長64KB),粒度爲字節(G=0)
; 屬於存儲器的段(S=1),是32位的段(D=1),目前位於內存中(P=1),段的特權級爲0(DPL=00),
; 是可讀可寫、向上擴展的數據段(TYPE=0010)
mov dword [bx+0x10],0x8000ffff
mov dword [bx+0x14],0x0040920b
; 創建gdt第#3號描述符,保護模式下的堆棧段描述符
; 該段:線性基地址爲0x0000 0000,段界限爲0x0 7A00,粒度爲字節(G=0)
; 屬於存儲器的段(S=1),是32位的段(D=1),目前位於內存中(P=1),段的特權級爲0(DPL=00),
; 是可讀可寫、向下擴展的數據段(TYPE=0010),在這裏是棧段
mov dword [bx+0x18],0x00007a00
mov dword [bx+0x1c],0x00409600
; 初始化描述符表寄存器GDTR
; 描述符表的界限(總字節數減1)。這裏共有4個描述符,每個描述符8字節,共31字節
mov word [cs:gdt_size+0x7c00], 31
; lgdt指令的操作數是一個48位(6字節)的內存區域,低16位是gdt的界限值,高32位是gdt的基地址
lgdt [cs:gdt_size+0x7c00]
; 打開地址線A20
; 芯片ICH的處理器接口部分,有一個用於兼容老式設備的端口0x92,端口0x92的位1用於控制A20
in al, 0x92
or al, 0000_0010B
out 0x92, al
; 禁止中斷
; 保護模式和實模式下的中斷機制不同,在重新設置保護模式下的中斷環境之前,必須關中斷
cli
; 開啓保護模式
; CR0的第1位(位0)是保護模式允許位(Protection Enabel, PE)
mov eax, cr0
or eax, 1
mov cr0, eax
; 清空流水線、重新加載段選擇器
; 遇到jmp或call指令,處理器一般會清空流水線,另一方面,還會重新加載段選擇器,並刷新描述符高速緩存器中的內容
; 建議:在設置了控制寄存器CR0的PE位之後,立即用jmp或call指令
jmp dword 0x0008:flush ; 不管是16位還是32位遠轉移,現在已經處於保護模式下,
; 處理器將把第一個操作數0x0008視爲段選擇子,而不是是模式下的邏輯段地址
; 段選擇子0x0008,即 0000_0000_00001_0_00(PRL爲00,TI爲0,索引號爲1)
; 當指令執行時,處理器加載CS,從GDT中取出相應的描述符加載到CS描述符高速緩存
; jmp dword, 32位的遠轉移指令。
; 16位的絕對遠轉移指令只有5字節,使用16位的偏移量,它會使標號flush的彙編地址相應地變小
[bits 32] ; 從進入保護模式開始,之後的指令都應當按32位操作數方式編譯
; 當處理器執行到這裏時,它會按32位模式進行譯碼
flush:
; 段選擇子:15~3位,描述符索引;2, TI(0爲GDT,1爲LDT); 1~0位,RPL(特權級)
mov cx, 0000_0000_00010_0_00B ; 已初始化的GDT中,數據段爲第2號描述符
mov ds, cx ; 當處理器執行任何改變段選擇器的指令時,就將指令中提供的索引號乘以8作爲偏移地址,同GDTR中提供的線性地址相加,
; 以訪問GDT,將找到的描述符加載到不可見的描述符高速緩存部分
; 以下在屏幕上顯示"Protect mode OK."
mov byte [0x00],'P'
mov byte [0x02],'r'
mov byte [0x04],'o'
mov byte [0x06],'t'
mov byte [0x08],'e'
mov byte [0x0a],'c'
mov byte [0x0c],'t'
mov byte [0x0e],' '
mov byte [0x10],'m'
mov byte [0x12],'o'
mov byte [0x14],'d'
mov byte [0x16],'e'
mov byte [0x18],' '
mov byte [0x1a],'O'
mov byte [0x1c],'K'
; 簡單驗證:32位下的棧操作,其默認的操作數大小是32位的
mov cx, 0000_0000_00011_0_00B ; 已初始化的GDT中,數據段爲第3號描述符
mov ss, cx
mov esp, 0x7c00
mov ebp, esp
push byte '.' ; 棧中壓入一個立即數(字節)
sub ebp, 4
cmp ebp, esp ; 驗證壓入立即數時,esp是否減4;若是,則顯示該立即數'.'
jnz halt
pop eax
mov byte [0x1e], al ; 顯示剛壓入的立即數'.'
halt:
hlt ; 已禁止中斷,將不會被喚醒
; lgdt指令的操作數是一個48位(6字節)的內存區域,低16位是gdt的界限值,高32位是gdt的基地址
gdt_size dw 0
gdt_base dd 0x00007e00
times 510-($-$$) db 0
db 0x55, 0xaa
第12章
別名技術;冒泡排序
; FILE: c12_mbr.asm
; DATE: 20200101
; TITLE:
; 設置堆棧段和指針
; 在32位處理器上,即使是在實模式下,也可以使用32位寄存器
mov eax, cs
mov ss, eax
mov sp, 0x7c00
; 計算gdt所在的邏輯段地址
; 64位的被除數在edx:eax中
mov eax, [cs:gdt_base+0x7c00]
xor edx, edx
mov ebx, 16
div ebx
mov ds, eax ; 商eax爲段地址,僅低16位有效
mov ebx, edx ; 餘數edx爲偏移地址,僅低16位有效
; 創建gdt第#0號描述符
; 處理器規定,gdt中第一個描述符必須是空描述符
mov dword [ebx], 0x00000000
mov dword [ebx+0x04], 0
; 創建gdt第#1號描述符,保護模式下的數據段描述符,對應0~4GB的線性地址空間
; 該段:線性基地址爲0,段界限爲0xF FFFF,粒度爲4KB
mov dword [ebx+0x08], 0x0000ffff
mov dword [ebx+0x0c], 0x00cf9200
; 創建gdt第#2號描述符,保護模式下的代碼段描述符
; 該段:線性基地址爲0x0000 7c00,段界限爲0x0 01FF,粒度爲1字節(G=0)
mov dword [ebx+0x10], 0x7c0001ff
mov dword [ebx+0x14], 0x00409800
; 別名alias
; 創建gdt第#3號描述符,以上代碼段的別名描述符
; 該段:線性基地址爲0x0000 7c00,段界限爲0x0 01FF,粒度爲1字節(G=0)
mov dword [ebx+0x18], 0x7c0001ff
mov dword [ebx+0x1c], 0x00409200
; 創建gdt第#4號描述符,保護模式下的堆棧段描述符
; 該段:線性基地址爲0x0000 7c00,段界限爲0xF FFFE,粒度爲4KB
mov dword [ebx+0x20],0x7c00fffe
mov dword [ebx+0x24],0x00cf9600
; 初始化描述符表寄存器GDTR
; 描述符表的界限(總字節數減1)。這裏共有5個描述符,每個描述符8字節,共40字節
mov word [cs:gdt_size+0x7c00], 39
; lgdt指令的操作數是一個48位(6字節)的內存區域,低16位是gdt的界限值,高32位是gdt的基地址
lgdt [cs:gdt_size+0x7c00]
; 打開地址線A20
; 芯片ICH的處理器接口部分,有一個用於兼容老式設備的端口0x92,端口0x92的位1用於控制A20
in al, 0x92
or al, 0000_0010B
out 0x92, al
; 禁止中斷
; 保護模式和實模式下的中斷機制不同,在重新設置保護模式下的中斷環境之前,必須關中斷
cli
; 開啓保護模式
; CR0的第1位(位0)是保護模式允許位(Protection Enabel, PE)
mov eax, cr0
or eax, 1
mov cr0, eax
; 清空流水線、重新加載段選擇器
; 遇到jmp或call指令,處理器一般會清空流水線,另一方面,還會重新加載段選擇器,並刷新描述符高速緩存器中的內容
; 建議:在設置了控制寄存器CR0的PE位之後,立即用jmp或call指令
jmp dword 0x0010:flush ; 不管是16位還是32位遠轉移,現在已經處於保護模式下,
; 處理器將把第一個操作數0x0010視爲段選擇子,而不是是模式下的邏輯段地址
; 段選擇子0x0010,即 0000_0000_00010_0_00(PRL爲00,TI爲0,索引號爲2)
; 當指令執行時,處理器加載CS,從GDT中取出相應的描述符加載到CS描述符高速緩存
; jmp dword, 32位的遠轉移指令。
; 16位的絕對遠轉移指令只有5字節,使用16位的偏移量,它會使標號flush的彙編地址相應地變小
[bits 32] ; 從進入保護模式開始,之後的指令都應當按32位操作數方式編譯
; 當處理器執行到這裏時,它會按32位模式進行譯碼
flush:
; 段選擇子:15~3位,描述符索引;2, TI(0爲GDT,1爲LDT); 1~0位,RPL(特權級)
; mov eax, 0x0018
mov eax, 0000_0000_00011_0_00B ; 已初始化的GDT中,代碼段別名描述符爲第3號描述符
mov ds, eax ; 當處理器執行任何改變段選擇器的指令時,就將指令中提供的索引號乘以8作爲偏移地址,同GDTR中提供的線性地址相加,
; 以訪問GDT,將找到的描述符加載到不可見的描述符高速緩存部分
mov eax, 0x0008 ; 已初始化GDT中,數據段爲第1號描述符
mov es, eax
mov fs, eax
mov gs, eax
; 設置堆棧段ss和段指針esp
mov eax, 0x0020 ; 已初始化GDT中,棧段爲第4號描述符
mov ss, eax
xor esp, esp
; 以下在屏幕上顯示"P.M. ok",顯示屬性爲默認的黑底白字0x07
mov byte [es:0xb8000], 'P'
mov byte [es:0xb8002], '.'
mov byte [es:0xb8004], 'M'
mov byte [es:0xb8006], '.'
mov byte [es:0xb8008], ' '
mov byte [es:0xb800a], ' '
mov byte [es:0xb800c], 'o'
mov byte [es:0xb800e], 'k'
; 冒泡排序,從小到大]
; 外循環n-1次。每執行一次外循環,內循環就會將一個數排到正確的位置,從而使下一次內循環少比較一次,所以
; 內循環的loop指令使用外循環的ecx值
mov ecx, gdt_size-string-1 ; 遍歷次數=串長度-1
@@1:
push ecx ; 32位模式下的loop次數用ecx
xor bx, bx ; 32位模式下,偏移量可以是16位
@@2:
mov ax, [string+bx]
cmp ah, al ; ah中存放的是高字節
jge @@3
xchg al, ah
mov [string+bx], ax
@@3:
inc bx
loop @@2
pop ecx
loop @@1
; 顯示排序後的字符串
mov ecx, gdt_size-string
xor ebx, ebx
mov ah, 0x07 ; 顯示屬性爲默認的黑底白字0x07
@@4:
mov al, [string+ebx]
mov [es:0xb80a0+ebx*2], ax ; 0xa0即160。顯存中,偏移量爲160的地方對應着屏幕第2行第1列
inc ebx
loop @@4
hlt ; hlt指令使處理器處於停機狀態
; 待排序的字符串
string db 's0ke4or92xap3fv8giuzjcy5l1m7hd6bnqtw.'
; lgdt指令的操作數是一個48位(6字節)的內存區域,低16位是gdt的界限值,高32位是gdt的基地址
gdt_size dw 0
gdt_base dd 0x00007e00
times 510-($-$$) db 0
db 0x55, 0xaa