《X86汇编语言从实模式到保护模式》第06章:相同的功能,不同的代码

针对第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

 

 

 

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值