编写主引导扇区代码
编写主引导扇区代码
DB 定义的变量为字节型 Define Byte
DW 定义的变量为字类型(双字节)Define Word
DD 定义的变量为双字型(4字节)Define Double Word
DQ 定义的变量为4字型(8字节)Define Quadra Word
DT 定义的变量为10字节型 Define Ten Byte
调试例子
在处理器加电或复位后硬盘如果是首选启动设备,bios 会读取硬盘 0 面 0 道 1 扇区(主引导扇区 MBR)主引导扇区有 512 字节,bios 将他加载到 0x0000:0x7c00 处,也就是物理地址的 0x07c00(有效的主引导扇区最后两字节应该是 0x55 和 0xaa),如果 bios 检测主引导扇区有效的话就会跳到 0x07c00 这里继续执行
0xB8000~0xBFFFF 这段物理地 址空间,是留给显卡的,由显卡来提供,用来显示文本
源码如下:
;代码清单5-1
;文件名:c05_mbr.asm
;文件说明:硬盘主引导扇区代码
;创建日期:2011-3-31 21:15
mov ax,0xb800 ;先让段寄存器es赋值为0xb800,不能将立即数传给段寄存器
mov es,ax ;CS是代码段,DS是数据段,SS是堆栈段,ES是附加段
;这样往里面写内容,以下显示字符串"Label offset:"
mov byte [es:0x00],'L' ;屏幕上两个连续的字节第一个是ASCII
mov byte [es:0x01],0x07 ;第二个是显示属性,低四位是前景色,高四位是背景色00000111
mov byte [es:0x02],'a'
mov byte [es:0x03],0x07
mov byte [es:0x04],'b'
mov byte [es:0x05],0x07
mov byte [es:0x06],'e'
mov byte [es:0x07],0x07
mov byte [es:0x08],'l'
mov byte [es:0x09],0x07
mov byte [es:0x0a],' '
mov byte [es:0x0b],0x07
mov byte [es:0x0c],"o"
mov byte [es:0x0d],0x07
mov byte [es:0x0e],'f'
mov byte [es:0x0f],0x07
mov byte [es:0x10],'f'
mov byte [es:0x11],0x07
mov byte [es:0x12],'s'
mov byte [es:0x13],0x07
mov byte [es:0x14],'e'
mov byte [es:0x15],0x07
mov byte [es:0x16],'t'
mov byte [es:0x17],0x07
mov byte [es:0x18],':'
mov byte [es:0x19],0x07
mov ax,number ;取得标号number的偏移地址
mov bx,10
;设置数据段的基地址
mov cx,cs
mov ds,cx
;求个位上的数字
mov dx,0
div bx ;商在ax,余数在dx
mov [0x7c00+number+0x00],dl ;保存个位上的数字
;求十位上的数字
xor dx,dx
div bx
mov [0x7c00+number+0x01],dl ;保存十位上的数字
;求百位上的数字
xor dx,dx
div bx
mov [0x7c00+number+0x02],dl ;保存百位上的数字
;求千位上的数字
xor dx,dx
div bx
mov [0x7c00+number+0x03],dl ;保存千位上的数字
;求万位上的数字
xor dx,dx
div bx
mov [0x7c00+number+0x04],dl ;保存万位上的数字
;以下用十进制显示标号的偏移地址
mov al,[0x7c00+number+0x04]
add al,0x30 ;万位上的数
mov [es:0x1a],al
mov byte [es:0x1b],0x04 ;颜色
mov al,[0x7c00+number+0x03]
add al,0x30
mov [es:0x1c],al ;千位
mov byte [es:0x1d],0x04
mov al,[0x7c00+number+0x02]
add al,0x30
mov [es:0x1e],al ;百位
mov byte [es:0x1f],0x04
mov al,[0x7c00+number+0x01]
add al,0x30
mov [es:0x20],al ;十位
mov byte [es:0x21],0x04
mov al,[0x7c00+number+0x00]
add al,0x30
mov [es:0x22],al ;个位
mov byte [es:0x23],0x04
mov byte [es:0x24],'D'
mov byte [es:0x25],0x07
infi: jmp near infi ;无限循环
number db 0,0,0,0,0
times 203 db 0
db 0x55,0xaa
用 bochsdbg 调试,start 之后来到一开始的位置,我们直接下个断点到 0x7c00,因为计算机启动后会把主引导程序加载到 0x7c00 处(约定没有为什么):b 0x7c00
然后让 bochs 继续执行到断点处:c
显示的是接下来要执行的指令,可以通过 s
单步执行
可以通过 r
来查看此时寄存器的值
首先是这两句,把 es 段寄存器赋值 0xb800,0xB8000~0xBFFFF,留给显卡显示字符
mov ax,0xb800 ;指向文本模式的显示缓冲区
mov es,ax
使用 sreg
查看段寄存器
然后从第 10 行到第 35 行就是往这里写字符了,把想要显示的字符放在对应的地址上,每个字符需要两个字节,前一个是字符,后一个是属性
比如:mov byte [es:0x00],'L'
mov byte [es:0x01],0x07
是字符 L,属性按照如下表格设置:
在这里 7 二进制是 111 也就是普通的白色
https://blog.csdn.net/gooding300/article/details/90127513
位 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|
属性 | 闪烁 | 背景R | 背景G | 背景B | 高亮 | R | G | B |
R | G | B | 颜色 |
---|---|---|---|
0 | 0 | 0 | 黑色 |
0 | 0 | 1 | 蓝色 |
0 | 1 | 0 | 绿色 |
0 | 1 | 1 | 青色 |
1 | 0 | 0 | 红色 |
1 | 0 | 1 | 粉色 |
1 | 1 | 0 | 黄色 |
1 | 1 | 1 | 白色 |
都写完之后使用 xp/7 0xb8000,看一下内存中是不是写上了
然后使用 mov ax,number
取 number 偏移地址 0x12e 放在 ax 中,这个偏移是相对于整个汇编程序的机器码的数量确定的,我们不好计算,是编译的时候自动替换的,在 bochs 中显示的是 mov ax, 0x012e
把它放在 ax 中作为被除数,然后把 bx 里放上 0xa 也就是十进制的 10 作为除数,用来分解每个位上的数,在 32 位下 dx:ax 放的是被除数,然后商放在 ax,余数放在 dx,这里把 dx 置为 0 只用 ax 就够了
接下来使用div bx
ax 中存的是 0x012e,除以 bx 的 0xa
也就是 302 除以 10,商是 30 也就是 0x1e 放在了 ax 中,余数是 2 放在了 dx 中
因为只用了一位,所以只需要拿 dl 中的值即可,这一步是把个位数放在 0x7c00+0x12e+0x00 那里
mov [0x7c00+number+0x00],dl
后面同样,操作不重复了。到第 70 行开始,从高位开始,取值放在显示文字那里
最后是一个 jmp 到他自己使得程序进入无限循环
number db 0,0,0,0,0
其中db
意思是声明字节(Declare Byte)会保留出 5 个字节的位置
DW(Declare Word)用于声明字数据,DD(Declare Double Word)用于声明双字(两个字)数据,DQ(Declare Quad Word)用于声明四字数据
第 102 行的times 203 db 0
中的 times 指令后面跟的两个参数是循环次数和执行的指令
在这里就是循环 203 次db 0
最后加一个 0x55 0xaa 表示主引导扇区结束(一个有效的主引导扇区,其最后两个字节的数据必须是0x55 和0xAA)
运行结果
改一下
既然第一次学,肯定得输出一个 hello world 才算圆满,但是这次用个不同的方法,把一堆数据放在开头声明出来
对应书中 c06_mbr.asm
jmp near start ;因为开头不是代码,所以跳转到代码的位置
mytext db 'H',0x07,'e',0x07,'l',0x07,'l',0x07,'o',0x07,' ',0x07,'W',0x07,\
'o',0x07,'r',0x07,'l',0x07,'d',0x07,':',0x07
number db 0,0,0,0,0
start:
mov ax,0x7c0 ;设置数据段基地址
mov ds,ax
mov ax,0xb800 ;设置附加段基地址
mov es,ax
cld ;将DF标志位清零,表示正向传送
mov si,mytext ;源地址的偏移地址
mov di,0 ;目标地址的偏移地址
mov cx,(number-mytext)/2 ;实际上等于 12
rep movsw
;得到标号所代表的偏移地址
mov ax,number
;计算各个数位
mov bx,ax
mov cx,5 ;循环次数
mov si,10 ;除数
digit:
xor dx,dx
div si
mov [bx],dl ;保存数位
inc bx
loop digit
;显示各个数位
mov bx,number
mov si,4
show:
mov al,[bx+si]
add al,0x30
mov ah,0x04
mov [es:di],ax
add di,2
dec si
jns show
mov word [es:di],0x0744
jmp near $
;在NASM编译器中$表示当前的汇编地址,$$表示当前段的起始汇编地址
times 510-($-$$) db 0
db 0x55,0xaa
在第 18 行,使用了rep movsw
指令来批量从一个地方复制到另一个地方,它的复制单位是以字为单位
还有个movsb
是传送以字节为单位,源地址段寄存器用 DS 偏移地址用 SI,即 DS:SI,目标地址段寄存器用 ES 偏移地址用 DI,即 ES:DI,要传送单位数用 CX 来确定。每次复制一个单位,SI 和 DI 地址都相应的加一个单位
除此之外还有 DF 方向标志位来确定他是从高地址往低地址复制(置为 1 时)还是从低地址往高地址复制(置为 0 时)cld 是置零。功能相反的是 std 置为一
所以这里的意思是:从数据段的 mytext 处复制到附加段从 0 开始处,复制的 12 个字,如果是用 movsb 的话就不用除 2 了,复制 24 个字节
后面循环分解位数也是用 CX 来保存循环的次数,inc
是加一,dec
是减一
定义栈
第七章介绍了用栈来存放数据
;代码清单7-1
;文件名:c07_mbr.asm
;文件说明:硬盘主引导扇区代码
;创建日期:2011-4-13 18:02
jmp near start
message db '1+2+3+...+100='
start:
mov ax,0x7c0 ;设置数据段的段基地址
mov ds,ax
mov ax,0xb800 ;设置附加段基址到显示缓冲区
mov es,ax
;以下显示字符串
mov si,message
mov di,0
mov cx,start-message
@g:
mov al,[si]
mov [es:di],al
inc di
mov byte [es:di],0x07
inc di
inc si
loop @g
;以下计算1到100的和
xor ax,ax
mov cx,1
@f:
add ax,cx
inc cx
cmp cx,100
jle @f
;以下计算累加和的每个数位
xor cx,cx ;设置堆栈段的段基地址
mov ss,cx
mov sp,cx
mov bx,10
xor cx,cx
@d:
inc cx
xor dx,dx
div bx
or dl,0x30
push dx
cmp ax,0
jne @d
;以下显示各个数位
@a:
pop dx
mov [es:di],dl
inc di
mov byte [es:di],0x07
inc di
loop @a
jmp near $
times 510-($-$$) db 0
db 0x55,0xaa
想要用栈需要定义好 ss 段寄存器和 sp 栈指针,这里全都是 0,等到实际用到的时候 sp 指针就是 0 - 2 = 0xfffe
查看栈上的值
print-stack 是 bochs 里面自带的查看栈的命令