[书]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万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章