[書]x86彙編語言:從實模式到保護模式 -- 第11章 進入保護模式,初識全局描述符表GDT; 第12章 別名,冒泡排序

第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

 

發佈了120 篇原創文章 · 獲贊 43 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章