80386保护模式下实现 Hello World!

学习自狄泰软件

利用选择子 拿到对应的段描述符,之后自然就可以拿到段基址,再加上段内偏移地址,就可以跳转
dword 双字 32位

在这里插入图片描述
word 两个字节 16位
dword 四个字节 32位

问题:为什么要明确的标识出 dword 32位呢?

答案:因为我们现在的无条件跳转 jmp 是从16位代码段 跳转到 32位代码段,16位代码段的意思就是 编译器在默认的情况下 对16位代码段里面的立即数 解释为16位。编译器有默认的准则,如果不明确指定,则在16位代码段中,编译器会默认 认为这个立即数就是16位的,多的部分就扔掉了。

如下代码段,在从16位代码段 跳转到 32位代码段的时候,没有明确的给编译器指定 dword !!,那么编译器就会按照16位代码段默认准则 即16位 进行编译,看到立即数,就按照16位的方式解释它,如果该立即数大过16位数据能够表示的范围,就会截断,所以这里 编译器就会认为跳转到 32位代码段 偏移地址为 0x5678的地方,1234 舍弃。

所以 dword 就是告诉编译器,现在要从16位跳转到 32位,因此解释这一行语句的时候,用 32位的方式来解释 !!!

在这里插入图片描述

显卡的主要任务是 提供要显示的数据,并且还有一个额外的智能:控制显示器的工作状态。当显卡控制完显示器,并提供好数据之后,显示器就可以把数据显示出来了。
显卡所提供的数据是放在现存中的,显存用来存储需要显示的数据。在本质上 和内存无差别,只不过显存是存在于显卡内部的。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
所以直接向这个地址范围内写数据,显存当中的内容就会发生改变,对应的屏幕当中的内容也会发生改变。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
自定义函数 需要一个栈空间,就是一段内存空间,这段内存空间在保护模式下 是需要被定义的,通过段描述符定义,所以可以在全局段描述符表中 新增一个 段描述符 ,代表全局的栈段。用于保护模式下的函数调用。

而我们需要打印的字符串可以放到 数据段,以往的学习表明 数据段就是 全局的一段数据空间,该空间存放数据,其中字符串也是放到全局数据段中的\,并且是放到全局只读数据段。 而这个所谓的全局数据段 本质就是一块内存,既然是一块内存,那就应该用段描述符定义一下,所以可以在 全局段描述符表中添加一个 段描述符,即数据段描述符

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
需要注意的是:80386以及之后的处理器 的16位实模式下,是可以使用32位寄存器 32位地址的,80386以及之后的处理器 虽然上电之后处于 8086的实模式,但是这是为了兼容8086 而 故意使用的 16位运行模式,即此时 对应的 立即数 或者寄存器 都使用16位,但这是为了兼容,实际80386以及之后的处理器 的16位实模式下 是可以使用32位寄存器和地址的!!!

loader.asm

%include "inc.asm"

org 0x9000

jmp CODE16_SEGMENT

[section .gdt]
; GDT definition
;                                 段基址,       段界限,       段属性
GDT_ENTRY       :     Descriptor    0,            0,           0
CODE32_DESC     :     Descriptor    0,    Code32SegLen - 1,    DA_C + DA_32
;显存段描述符
;显存段属性:DA_DRWA + DA_32 --> 32位保护模式下的段,并且该段 可读可写 并且在这一刻之前 显存已经被访问过了(因为之前BIOS需要打印的信息 都是通过操作显存 显卡完成的)
VIDEO_DESC      :     Descriptor 0xB8000,     0x07FFF,         DA_DRWA + DA_32

;全局数据段 段描述符
;全局数据段属性 :32位保护模式下 只读段
DATA32_DESC     :     Descriptor    0,    Data32SegLen - 1,    DA_DR + DA_32

;全局栈空间 段描述符
;全局栈空间属性 :32位保护模式下 可读可写段   范围 0 - 0x7c00 定义为32位保护模式下全局栈空间
STACK_DESC      :     Descriptor    0,     TopOfStackInit,     DA_DRW + DA_32
; GDT end

GdtLen    equ   $ - GDT_ENTRY

GdtPtr:
          dw   GdtLen - 1
          dd   0
          
          
; GDT Selector

Code32Selector    equ (0x0001 << 3) + SA_TIG + SA_RPL0
;定义 显存段描述符VIDEO_DESC 的选择子,在全局段描述符表中的下标是2  所以段描述符索引值是 2
;属性是 SA_TIG(表示该选择子想要去访问的段描述符是 全局段描述符) + SA_RPL0(特权级 第0特权级)
VideoSelector     equ (0x0002 << 3) + SA_TIG + SA_RPL0

;定义 全局数据段 段描述符DATA32_DESC的 选择子,在全局段描述符表中的下标是3  所以段描述符索引值是 3
;属性是 SA_TIG(表示该选择子想要去访问的段描述符是 全局段描述符) + SA_RPL0(特权级 第0特权级)
Data32Selector    equ (0x0003 << 3) + SA_TIG + SA_RPL0

;定义 栈空间 段描述符STACK_DESC的 选择子,在全局段描述符表中的下标是4  所以段描述符索引值是 4
StackSelector     equ (0x0004 << 3) + SA_TIG + SA_RPL0

; end of [section .gdt]

; 定义常量  初始栈指针位置
TopOfStackInit    equ 0x7c00

;定义 32位保护模式 数据段,存放字符串
[section .dat]
[bits 32]
DATA32_SEGMENT:
	;定义需要打印的字符串 以0结束
    DTOS               db  "D.T.OS!", 0
	;字符串的长度,此时代表字符串的 段内偏移地址
    DTOS_OFFSET        equ DTOS - $$
    HELLO_WORLD        db  "Hello World!", 0
    HELLO_WORLD_OFFSET equ HELLO_WORLD - $$

;数据段长度 
Data32SegLen equ $ - DATA32_SEGMENT

[section .s16]
[bits 16]
CODE16_SEGMENT:
    mov ax, cs
    mov ds, ax
    mov es, ax
    mov ss, ax
	;在16位 代码段中 设置了栈指针
    mov sp, TopOfStackInit
    
; initialize GDT for 32 bits code segment
;设置 CODE32_SEGMENT段 32位段基址 到  对应的段描述符对应位置
    mov esi, CODE32_SEGMENT
    mov edi, CODE32_DESC
    
    call InitDescItem
    
;设置 数据段 32位段基址 到  数据段 段描述符对应位置
    mov esi, DATA32_SEGMENT
    mov edi, DATA32_DESC
    
    call InitDescItem
    
    ; initialize GDT pointer struct
    mov eax, 0
    mov ax, ds
    shl eax, 4
    add eax, GDT_ENTRY
    mov dword [GdtPtr + 2], eax

    ; 1. load GDT
    lgdt [GdtPtr]
    
    ; 2. close interrupt
    cli 
    
    ; 3. open A20
    in al, 0x92
    or al, 00000010b
    out 0x92, al
    
    ; 4. enter protect mode
    mov eax, cr0
    or eax, 0x01
    mov cr0, eax
    
    ; 5. jump to 32 bits code
    jmp dword Code32Selector : 0

;初始化段描述符中的 段基址, 根据代码段标签初始化对应的段描述符
; esi    --> code segment label  代码段标签
; edi    --> descriptor label 段描述符标签
InitDescItem:
;备份 eax
    push eax

	; 设置32位段基址 到 段描述符对应位置
    mov eax, 0
    mov ax, cs
	;eax 左移4位
    shl eax, 4
	;代码段标签
	;段寄存器<<4) + (32位代码段 偏移地址   == 32位代码段的真实物理地址,即段基地址
    add eax, esi
	;将 ax寄存器保存的值 放到 段描述符标签+2 地址处, 段基址的0-15位 放到 段描述符的16-31 位
    mov word [edi + 2], ax
	;eax 右移16位  也就是低16位被移出去了
    shr eax, 16
	; al(ax低8位)   此时al是32位代码段基地址当中的第三个字节(段基址 16-23位) 放到 段描述符的32-39位
    mov byte [edi + 4], al
	; ah(ax高8位)    此时ah是32位代码段基地址当中第四个字节(段基址 24-31位) 放到 段描述符的55-63位 第七个字节
    mov byte [edi + 7], ah
   
;回复 eax   
    pop eax
    
    ret
    
    
[section .s32]
[bits 32]
CODE32_SEGMENT:
;首先分别将 
	;显存段 基地址放到 gs段寄存器中,gs段寄存器用于存放 显存段基地址
	;栈空间段 基地址放到 ss段寄存器中,ss段寄存器用于存放 栈空间段基地址
	;数据空间段 基地址放到 ds段寄存器中,ds段寄存器用于存放 数据段基地址

; 将显存段描述符的选择子 放到 gs寄存器中,所以此时gs寄存器就代表了这整个显存段
    mov ax, VideoSelector
    mov gs, ax
   
	;通过栈空间段描述符的选择子 获取段的起始地址 设置到 ss
    mov ax, StackSelector
    mov ss, ax
 
;通过段描述符的选择子 获取段的起始地址 即ds
;再通过具体的段内偏移地址ebp , 得到具体地址  ds:ebp
	;通过段描述符的选择子 设置 对应段寄存器,即获取数据段 段地址
    mov ax, Data32Selector
    mov ds, ax
    
	;获取目标字符串在段内偏移地址, 因为是保护模式下的编程,地址用段内偏移地址
    mov ebp, DTOS_OFFSET
	;打印属性 黑底红字
    mov bx, 0x0C
	;打印位置
    mov dh, 12
    mov dl, 33
    
    call PrintString
    
    mov ebp, HELLO_WORLD_OFFSET
    mov bx, 0x0C
    mov dh, 13
    mov dl, 31
    
    call PrintString
    
    jmp $

; 32位保护模式下下的 打印函数 需要定义在 [section .s32] 代码节下
; ds:ebp    --> string address  目标字符串地址
; bx        --> attribute  打印属性
; dx        --> dh : row, dl : col  打印坐标
PrintString:
    push ebp
    push eax
    push edi
    push cx
    push dx


print:
	;取当前字符
    mov cl, [ds:ebp]
	;判断当前字符是否为0
    cmp cl, 0
	;如果是0 则打印结束  跳转到 end标签
    je end
	;如果不为0 
	; 行*80,相乘结果放到 eax寄存器中
    mov eax, 80
    mul dh
	
	;加列坐标 dl  -->  eax+列
    add al, dl
	; eax*2  得到最终的打印位置
    shl eax, 1
	;将打印位置 放到 edi 中
    mov edi, eax
	;获取字符属性 - 高八位  需要显示字符的颜色
    mov ah, bl
	;获取字符属性 - 低八位  需要显示的字符 
    mov al, cl
	;将字符写入显存中 打印
    mov [gs:edi], ax
	;下一个字符
    inc ebp
	;下一个字符打印的位置
    inc dl
	;无条件跳转 循环
    jmp print

end:
    pop dx
    pop cx
    pop edi
    pop eax
    pop ebp
    
    ret
    
Code32SegLen    equ    $ - CODE32_SEGMENT

猜你喜欢

转载自blog.csdn.net/LinuxArmbiggod/article/details/120734625