跳转至

编写主引导扇区代码

编写主引导扇区代码

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

1637060045460-2cdf5290-2359-4d64-aff7-7502face3471.png

显示的是接下来要执行的指令,可以通过 s 单步执行

可以通过 r 来查看此时寄存器的值

1637060062616-13b53517-e003-4305-88a2-60a3e13da431.png

首先是这两句,把 es 段寄存器赋值 0xb800,0xB8000~0xBFFFF,留给显卡显示字符

mov ax,0xb800                 ;指向文本模式的显示缓冲区
mov es,ax

使用 sreg 查看段寄存器

1637064297953-a2c5484b-347a-4365-b8cb-754a5e2f31d4.png

然后从第 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,看一下内存中是不是写上了

1637064883300-cde35e85-cffa-4090-80f4-9ffbd6da529a.png

然后使用 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

1637140400144-693730ce-2b39-46e3-8e94-d967bde5d40c.png

也就是 302 除以 10,商是 30 也就是 0x1e 放在了 ax 中,余数是 2 放在了 dx 中

1637140404401-ba299d05-290f-4c65-83a8-5205a3edb5da.png

因为只用了一位,所以只需要拿 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)

运行结果

1637063918116-f41203f4-0499-481e-aa90-c1ca36ee24e9.png

改一下

既然第一次学,肯定得输出一个 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是减一

1637372989947-48aaf388-34a2-4ac5-b019-12af20fabc31.png

定义栈

第七章介绍了用栈来存放数据

         ;代码清单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

查看栈上的值

1657980160881-ef91561b-ad9a-4df7-b215-9e32d237587a.png

print-stack 是 bochs 里面自带的查看栈的命令

1657981104987-e0f15b3b-00db-4e71-8f87-9a8449fd8e4d.png

原文: https://www.yuque.com/hxfqg9/bin/gmbwuw