参考书籍《0x86从实模式到保护模式》
1.实模式显示字符
首先,电脑开机进入系统,加载bios,然后bios完成一些硬件的初始化,从磁盘读取mbr到绝对地址0x7c00处,然后跳转至0x7c00,此时屏幕的显示模式默认为80*25(一行80个字符,共25行)。
如何对屏幕输出字符?很简单,这个模式的缓冲区地址为0xb8000(0xb8000-0xb8f9f),屏幕上一个字符的这里占两个字节,第一个字节前高4位说明这个字符的背景色,低4位说明字符的颜色,第二个字节是ascii码字符。因此总共占用的内存为80*25*2=4000字节。光知道如何显示字符还不行,还需要知道光标并可以设置光标的位置,光标储存在显卡内的两个8位寄存器内,可以利用端口来读取这个两个寄存器,首先,将寄存器索引存入0x03d4,这两个寄存器的索引号分别为0xe和0xf
例:
mov dx,0x3d4
mov al,0xe
out dx,al
通过0x03d5来读取相应寄存器的值
例:
mov dx,0x3d5
in al,dx
完整获取光标位置的代码如下
;获取光标位置,储存在ax中
get_pos:
push dx
push bx
mov al,0x0e
mov dx,0x03d4
out dx,al
mov dx,0x03d5
in al,dx
mov bl,al
mov al,0x0f
mov dx,0x3d4
out dx,al
mov dx,0x03d5
in al,dx
mov ah,bl
pop bx
pop dx
ret
要注意,ax是作为一个整体的值,并非ah存行数,al存列数。简单讲,ax*2+0xb8000指向这个字符在内存中的位置。设定光标位置很简单,在0x3d4存入索引值后,有out指令向0x3d5写入光标的位置
例:
;修改光标位置
set_pos:
push dx
push bx
mov bx,ax
mov dx,0x03d4
mov al,0xe
out dx,al
mov dx,0x03d5
mov al,bh
out dx,al
mov dx,0x03d4
mov al,0x0f
out dx,al
mov dx,0x03d5
mov al,bl
out dx,al
mov ax,bx
pop bx
pop dx
ret
之后我们要做的就是根据光标的位置,向内存写入正确的字符。
;*****************************************************
;依次push进字符串地址,字符数,背景色前景色
STRING equ 0x18 ;字符串地址
SIZE equ 0x16;字符数量
COLOR equ 0x14;背景色前景色
print:
pusha
push bp
mov bp,sp
mov ax,0xb800
mov es,ax
mov di,word [bp+STRING] ;di字符串地址
mov cx,word [bp+SIZE] ;cx字符数
mov dx,word [bp+COLOR] ;颜色
call get_pos
outstr:
mov dl,[di]
call judeg
inc di
loop outstr
end_print:
pop bp
popa
ret
;-------------------------------------------------------
judeg:
judge_back: ;判断退格
cmp dl,0x08
jz ch_back
jmp judge_tab
ch_back:
cmp ax,0
jle next
dec ax
call set_pos
imul bx,ax,2
mov word [es:bx],0
jmp next
;-------------------------------------------------------
judge_tab: ;判断制表符
cmp dl,0x09
jz ch_tab
jmp judge_enter
ch_tab:
mov dl,8
mov bx,ax
div dl
mov al,8
sub al,ah
xor ah,ah
add bx,ax
mov ax,bx
call set_pos
jmp next
;-------------------------------------------------------
judge_enter: ;判断回车
cmp dl,0x0d
jz ch_enter
jmp judge_newline
ch_enter:
push dx
mov bx,ax
mov dl,80
div dl ;ax储存商,dx储存余数
pop dx
shr ax,8
sub bx,ax
mov ax,bx
call set_pos
jmp next
;--------------------------------------------------------
judge_newline: ;判断换行
cmp dl,0x0a
jz ch_newline
jmp judge_ch
ch_newline:
cmp ax,1840
jae calup
jmp n1
calup:
call roll_up
jmp next
n1:
add ax,80
call set_pos
jmp next
;---------------------------------------------------------
judge_ch: ;判断是否为可输出字符,小于0x20,大于0x7e不可输出
cmp dl,0x20
jl next
cmp dl,0x7e
ja next
mov_cursor:
inc ax
call set_pos
outch:
mov bx,ax
dec bx
imul bx,2
mov word [es:bx],dx
next:
ret
;-----------------------------------------------------------
;向上滚动一行
roll_up:
push ds
push es
push cx
push di
mov bx,0xb800
mov ds,bx
mov bx,0xb7f6
mov es,bx
xor di,di
xor si,si
mov cx,2000 ;共传送80*25字
rep movsw
pop di
pop cx
pop es
pop ds
ret
调用方法例:
push msg ;msg为字符串首地址
push 20 ;字符数
push 0x2f00 ;颜色设定为绿底白字
call print
msg db "hello,world!"
结果:
下面是背景色、前景色说明
背景色 | 前景色 |
0=黑色 | 0=黑色 |
1=蓝色 | 1=蓝色 |
2=绿色 | 2=绿色 |
3=青色 | 3=青色 |
4=红色 | 4=红色 |
5=紫红 | 5=紫红 |
6=橙色 | 6=橙色 |
7=浅灰 | 7=浅灰 |
8=黑色+闪烁 | 8=深灰 |
9=蓝色+闪烁 | 9=紫色 |
a=绿色+闪烁 | a=亮绿 |
b=青色+闪烁 | b=亮青 |
c=红色+闪烁 | c=亮红 |
d=紫红+闪烁 | d=亮紫红 |
e=橙色+闪烁 | e=亮黄 |
f=白色+闪烁 | f=白色 |
2.实模式下的中断
实模式下的中断非常简单,中断向量表存放在0x0~0x3ff之间,每4字节为一个表项,每个表项的值指向一段程序的入口,这段程序就是处理中断的程序。例如,使用了int 0指令,cpu就会进入0x0~0x3指向的地址,然后执行这里的程序。地址的表示使用小端字节序,前两个字节为偏移地址,后两个地址为段地址。比如地址0起始的4个字节为0x53,0xff,0x00,0xf0,段地址为0xff00,偏移地址为0xff53,实际地址就是0xf000*0x10+0xff53=0xfff53。这里就是说明使用int 0指令后会转向起始地址为0xfff53的程序。
编写中断程序时要注意,调用中断时,cpu会向栈中依次压入flag标志位,cs,ip,要用iret来返回。