NASM(四)
任务:向屏幕输送大量文本
屏幕光标控制
光标在屏幕上的位置保存在显卡内部的两个光标寄存器中,每个寄存器是8位的,合起来形成一个16位的数值。标准VGA文本模式是25行,每行80个字符。当光标在屏幕右下角时,该值为25×80-1=1999
。
显卡操作十分复杂,寄存器多,寄存器的访问只能通过索引寄存器间接访问。索引寄存器的端口号是0x3d4
,可以向它写入一个值,用来指定内部的某个寄存器。两个8位的光标寄存器,其索引值分别是14(0x0e
)和15(0x0f
),分别用于提供光标位置的高8位和低8位。指定了寄存器之后,通过数据端口0x3d5
来进行进行读写。
get_cursor:
mov dx, 0x3d4 ; 索引寄存器
mov al, 0x0e ; 0x0e光标寄存器
out dx, al ; 选择 0x0e光标寄存器, 高8位
mov dx, 0x3d5 ; 读数据
in al, dx ; 读
mov ah, al ; 放到高8位
mov dx, 0x3d4 ; 索引寄存器
mov al, 0x0f ; 0x0f光标寄存器
out dx, al ; 选择 0x0e光标寄存器, 高8位
mov dx, 0x3d5 ; 读数据
in al, dx ; 读
; mov bx, ax ; 把光标信息存储在bx中
set_cursor:
mov dx,0x3d4 ; 索引寄存器
mov al,0x0e ; 0x0e光标寄存器
out dx,al ; 选择0x0e光标寄存器, 高8位
mov dx,0x3d5 ; 选择0x3d5,准备写
mov al,bh ; 准备数据
out dx,al ; 写
mov dx,0x3d4
mov al,0x0f
out dx,al
mov dx,0x3d5
mov al,bl
out dx,al
回车符与换行符
回车符0x0d
,将光标移动到当前行的行首。$(光标位置 // 80) * 80$。
mov bl, 80 ; 判断行列
div bl ; 被除数存储在"AX"寄存器中 "AL"中得到的是当前行的行号。
; 商(结果的整数部分)将存储在"AL"寄存器中
; 余数(结果的小数部分)将存储在"AH"寄存器中
mul bl ; 乘数应存储在AL寄存器中
; 结果的低8位存储在"AL"寄存器中
; 结果的高8位存储在"AH"寄存器中
mov bx, ax ; 保存到bx中
换行符0x0a
, 换行的意图是向下挪一行,$光标位置+80$。
add bx, 80 ; 换行
换行后,若超出显示范围,需要进行回滚,空出最后一行。
cmp bx, 2000
jl set_cursor ; 屏幕只能显示2000个字符, 多余的滚行
mov ax,0xb800
mov ds,ax
mov es,ax
cld ; 清除标志位,使movsw向正方向前进
mov si,0xa0
mov di,0x00
mov cx,1920 ; 位置前挪,空出最后一行
rep movsw ; 该指令需要配合源地址寄存器(SI),目标地址寄存器(DI)和计数寄存器(CX)一起使用
; 它会根据CX寄存器中的计数值重复执行movsw指令
clean: ; 最后一行全部置为空格
mov bx, 3840 ; 屏幕上第25行第1列在显存中的偏移地址是3840
mov cx, 80
mov word[es:bx], 0x0720 ; 清除第25行的内容
add bx, 2
loop clean
mov bx,1920 ; 设置光标
文字输出
将文字输出到屏幕,此代码在主引导扇区中运行,注意主引导扇区为512字节。
init:
mov ax, 0x7c0
mov ds, ax ; 设置数据段
mov ax, msg0
mov si, ax ; 设置源数据地址
mov ax, 0
mov ss, ax ; 设置栈段
mov sp, ax
mov bx, 0 ; 先将鼠标指针设置为0
call set_cursor ;
mov di, 0 ; 偏移
mov ax, end_data - data
put:
mov cl, [ds:si] ; 拿字符
add si, 1 ; 偏移+1
push si ; 入栈保存
push ax ; 入栈保存
call put_char
pop ax
pop si
dec ax
cmp ax, 0 ; 判断数据是否传输完
jne put
exit:
jmp near exit
get_cursor:
mov dx, 0x3d4 ; 索引寄存器
mov al, 0x0e ; 0x0e光标寄存器
out dx, al ; 选择 0x0e光标寄存器, 高8位
mov dx, 0x3d5 ; 读数据
in al, dx ; 读
mov ah, al ; 放到高8位
mov dx, 0x3d4 ; 索引寄存器
mov al, 0x0f ; 0x0f光标寄存器
out dx, al ; 选择 0x0e光标寄存器, 高8位
mov dx, 0x3d5 ; 读数据
in al, dx ; 读
; mov bx, ax ; 把光标信息存储在bx中
ret
put_char: ; 接受参数cl, 用于提供要显示的ASCII码。
call get_cursor
cmp cl, 0x0d ; 判断是否是0x0d
jne check_0x0a ; 判断是否是0x0a
mov bl, 80 ; 判断行列
div bl ; 被除数存储在"AX"寄存器中 "AL"中得到的是当前行的行号。
; 商(结果的整数部分)将存储在"AL"寄存器中
; 余数(结果的小数部分)将存储在"AH"寄存器中
mul bl ; 乘数应存储在AL寄存器中
; 结果的低8位存储在"AL"寄存器中
; 结果的高8位存储在"AH"寄存器中
mov bx, ax ; 保存到bx中
jmp set_cursor
check_0x0a:
cmp cl, 0x0a
jne put_c
add bx, 80 ; 换行
cmp bx, 2000
jl set_cursor ; 屏幕只能显示2000个字符, 多余的滚行
mov ax,0xb800
mov ds,ax
mov es,ax
cld ; 清除标志位,使movsw向正方向前进
mov si,0xa0
mov di,0x00
mov cx,1920 ; 位置前挪,空出最后一行
rep movsw ; 该指令需要配合源地址寄存器(SI),目标地址寄存器(DI)和计数寄存器(CX)一起使用
; 它会根据CX寄存器中的计数值重复执行movsw指令
clean: ; 最后一行全部置为空格
mov bx, 3840 ; 屏幕上第25行第1列在显存中的偏移地址是3840
mov cx, 80
mov word[es:bx], 0x0720 ; 清除第25行的内容
add bx, 2
loop clean
mov bx,1920 ; 设置光标
set_cursor:
mov dx,0x3d4 ; 索引寄存器
mov al,0x0e ; 0x0e光标寄存器
out dx,al ; 选择0x0e光标寄存器, 高8位
mov dx,0x3d5 ; 选择0x3d5,准备写
mov al,bh ; 准备数据
out dx,al ; 写
mov dx,0x3d4
mov al,0x0f
out dx,al
mov dx,0x3d5
mov al,bl
out dx,al
ret
put_c:
mov ax,0xb800
mov es,ax
shl bx,1 ; bx 乘2
mov byte [es:bx], cl
inc bx
mov byte [es:bx], 0x07 ; 白字黑底
dec bx
shr bx,1 ; bx 除2
add bx,1
jmp set_cursor
data:
msg0 db 'This is NASM - the famous Netwide Assembler. ',0x0d,0x0a
db 'Back at SourceForge and in intensive development! ',0x0d,0x0a
db 'Get the current versions from http://www.nasm.us/.',0x0d,0x0a
db 0x0d,0x0a,0x0d,0x0a
db ' Example code for calculate 1+2+...+1000:',0x0d,0x0a,0x0d,0x0a
db ' xor dx,dx',0x0d,0x0a
db ' xor ax,ax',0x0d,0x0a
db ' xor cx,cx',0x0d,0x0a
db ' ...'
end_data:
times 510-($-$$) db 0
db 0x55
db 0xaa
Comments | NOTHING