针对第5章的代码,可以说是直截了当,直接对内存进行了写。这一章通过使用循环和条件转移指令来实现,知识点都是一样的,
直接上代码,然后再分析每一行代码:
既然功能相同,直接运行下程序,得到运行结果:
总共去敲这3条命令,感觉有点麻烦,所以,我把这三条命令写在脚本里,每次修改代码,只需要运行下脚本sh run.sh就行了。
#!/bin/bash
rm c06_mbr.bin
rm boot.img
nasm -o c06_mbr.bin c06_mbr.asm
dd if=c06_mbr.bin of=boot.img bs=512 count=1
qemu-system-i386 boot.img
;代码清单6-1
;文件名:c06_mbr.asm
;文件说明:硬盘主引导扇区代码
;创建日期:2011-4-12 22:12
jmp near start
mytext db 'L',0x07,'a',0x07,'b',0x07,'e',0x07,'l',0x07,' ',0x07,'o',0x07,\
'f',0x07,'f',0x07,'s',0x07,'e',0x07,'t',0x07,':',0x07
number db 0,0,0,0,0
start:
mov ax,0x7c0 ;设置数据段基地址
mov ds,ax
mov ax,0xb800 ;设置附加段基地址
mov es,ax
cld
mov si,mytext
mov di,0
mov cx,(number-mytext)/2 ;实际上等于 13
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 $
times 510-($-$$) db 0
db 0x55,0xaa
代码的第一句是
jmp near start
跳转到汇编语言的标号start处开始执行,这里的start只是一个约定俗成的标号,表示跳转到start开始执行,这不同于C语言的main关键字,汇编是按照顺序执行的,这样的话,你可以把这个标号改成任意标号的比如,比如改成go,同样的,也把start:处的标号改成go。不影响程序的执行,同样的效果。
mov ax,0x7c0 ;设置数据段基地址
mov ds,ax
mov ax,0xb800 ;设置附加段基地址
mov es,ax
汇编语言不允许直接将立即数给段寄存器,需要通过通用寄存器转一下,设置段寄存器和附加段寄存器。
为什么ds要设置成0X7c0?
在初始化的时候cs,ds都为0,x86计算机放置的第一条指令是在内存0X7C00处,这时在0X7c00处表示为0000:7c00,将段地址左移4位再加上0x7c00得到第一条指令。
比如:保存dl值到0x7c00写法如下,简单粗暴
mov [0x7c00+0x00],dl
同样一个段,如果我们将段起始地址设置为0x7c00,在0x7c00处就可以表示为0x7c0:0000,将段地址左移4位加上偏移量,也是得到0x7C00,
这时,存储dl到0x7C00处的写法如下,是不是清爽点点。
mov [0x00],dl
接着看代码
cld
mov si,mytext
mov di,0
mov cx,(number-mytext)/2 ;实际上等于 13
rep movsw
cld是清除 FLAGS标志位 DF,
clear direction flag的简写。
执行CLD后,DF=0, 每次迭代后,指令通过增加数据指针来工作。与之对应的指令是STD(set direction flags),设置后DF=1。每次迭代后数据指针通过递减来工作。
为什么要把这句放在这里?cld这句汇编是为了rep movsw这句服务的,我觉得应该放在cld之前更便于理解。
接下来的3句就是设置si,di,cx的值。在汇编语言中,标号表示的是内存单元的地址,si表示mytext的地址,cx表mytext内存长度的一半。
rep movsw Move (E)CX words from [(E)SI] to ES:[(E)DI],表示从ES:DI移动CX个字节到ES:SI中。
rep 是repeat的缩写,表示重复,这是一个汇编语言前缀,表示重复执行rep后面的语句,
movsw 是mov string word,以字节为单位移动字符。要移动多少次呢,这个就是CX寄存器来规定的。由于移动的是word,所以移动(number-mytext)/2次。
计算机访问内存是按照字节来访问的,如果每次移动一个字节byte(movsb),那就需要移动(number-mytext)次。所以上面语句等同于
mov si,mytext
mov di,0
mov cx,(number-mytext)
cld
rep movsb
这里还有一个需要说明的点是mov di,0,相当于移动到ES:DI(0xb8000)的位置,但是在qemu中先要打印bios的信息,已经在偏移0处打印了,后续再打印不就覆盖了吗,那我们就把偏移往后移动,打在另外的地方,先随便该个地方试试mov di,100
虽然偏移了,但是偏移不对,我们用的是80*25的显示器,一行可以显示80个字符,总共显示25行。那么每一行的偏移就是160byte,如果从第二行开始显示,应该就是偏移160,试试。
good,我们把字符移动到booting from hard Disk...的下一行,这里总共8行,就是160*8,把这句换成mov di,160*8。
接着看代码
;得到标号所代表的偏移地址
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
前面4行mov指令不用多多说,就是设置值,但是到目前位置,你是不知道number是多少的。只知道bx是一个偏移地址,cx等于5,si等于10. 接着是digit标号。
xor dx,dx
xor Destination,Source,异或的结果放在Destination中。Destination = Destination ^ Source;
自己与自己相同,结果为0,相当与清零操作,效果等同于 mov dx, 0,mov涉及取数,放寄存器,异或是单周期操作效率更高。
div si
div value
div是除法简写,除法涉及到被除数,除数,商,余数。在汇编语言中,被除数放在ax中,除数放在value中,但是这个value是个寄存器,不能是个立即数,如果写成div 10 是非法操作。执行完div后的结果,商放在ax,余数放在dx中。
mov [bx],dl ;保存数位
将dl的值放在bx所指向的内存地址中,这里可以把bx理解成数组下标,计算完一次,就增加一次bx的值用于存放下一个值。
然后跳转
loop digit
循环执行,但是要循环多少次呢?这里由CX决定的,CX中的C是count的意思。每执行一次循环,cx就减1,直到cx为0,这里执行5次除法。这里作者也是试出来的,其实可以把他改成99次,换成3就会显示错误。
;显示各个数位
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
这里是显示,最前面的2句是赋值语句,bx等于number的编号,si等于4.
show:
...
jns show
JNS - Jump No Sign (positive value) 符号标识,如果是负数就
jns--Jump if sign flag not set (jump if signed number is positive)如果符号标志位未设置,即为正数就跳转。符号标志位SF存在与FLAGS寄存器中。
在代码中就算是dec si这条语句,每次执行一次循环就减去1,如果是负数了,则会设置SF标志位,停止跳转。
也是和循环一样,如果改成循环则是:
mov cx,5
show:
mov al,[bx+si]
add al,0x30
mov ah,0x04
mov [es:di],ax
add di,2
loop show
作者就是为了让你多熟悉下jns的用法,在调试的时候,也可以看看FLAGS的标志位
add al,0x30 ;将数字转化成ASCII字符,放在al中
mov ah,0x44 ;将在显示器的显示格式放在ah中
mov [es:di],ax ;ah和al组成Ax,放在es段的di偏移
add di,2 ;放置完一个字符串和显示格式,移动di,放置下一个字符。
mov word [es:di],0x0744
放置字符D,字符D的ascii为0x44,显示格式为0x07
jmp near $
$ 标志表示当前位置,表示跳转到当前指令。
$$ 标志表示汇编代码的开始位置。
$-$$就可以表示这段汇编代码占据了多大的空间。
times 510-($-$$) db 0
db 0x55,0xaa
MBR是放在第一个扇区的512字节,除去最后的AA,55两字节,还剩下510字节,其中汇编占据$-$$字节,那就还剩下510-($-$$)字节。
这个空间我们填写无用数据,写啥都行,反正也用不到,
最后两个字节一定要是0x55,0xAA。
最后执行结果如下:
虽然程序执行完了,还是由可以修改的地方的,比如修改number的值得到
end