在处理器加电或复位后硬盘如果是首选启动设备,bios 会读取硬盘 0 面 0 道 1 扇区(主引导扇区 MBR)主引导扇区有 512 字节,bios 将他加载到 0x0000:0x7c00 处,也就是物理地址的 0x07c00(有效的主引导扇区最后两字节应该是 0x55 和 0xaa),如果 bios 检测主引导扇区有效的话就会跳到 0x07c00 这里继续执行
0xB8000~0xBFFFF 这段物理地 址空间,是留给显卡的,由显卡来提供,用来显示文本
源码如下:

  1. ;代码清单5-1
  2. ;文件名:c05_mbr.asm
  3. ;文件说明:硬盘主引导扇区代码
  4. ;创建日期:2011-3-31 21:15
  5. mov ax,0xb800 ;先让段寄存器es赋值为0xb800,不能将立即数传给段寄存器
  6. mov es,ax ;CS是代码段,DS是数据段,SS是堆栈段,ES是附加段
  7. ;这样往里面写内容,以下显示字符串"Label offset:"
  8. mov byte [es:0x00],'L' ;屏幕上两个连续的字节第一个是ASCII
  9. mov byte [es:0x01],0x07 ;第二个是显示属性,低四位是前景色,高四位是背景色00000111
  10. mov byte [es:0x02],'a'
  11. mov byte [es:0x03],0x07
  12. mov byte [es:0x04],'b'
  13. mov byte [es:0x05],0x07
  14. mov byte [es:0x06],'e'
  15. mov byte [es:0x07],0x07
  16. mov byte [es:0x08],'l'
  17. mov byte [es:0x09],0x07
  18. mov byte [es:0x0a],' '
  19. mov byte [es:0x0b],0x07
  20. mov byte [es:0x0c],"o"
  21. mov byte [es:0x0d],0x07
  22. mov byte [es:0x0e],'f'
  23. mov byte [es:0x0f],0x07
  24. mov byte [es:0x10],'f'
  25. mov byte [es:0x11],0x07
  26. mov byte [es:0x12],'s'
  27. mov byte [es:0x13],0x07
  28. mov byte [es:0x14],'e'
  29. mov byte [es:0x15],0x07
  30. mov byte [es:0x16],'t'
  31. mov byte [es:0x17],0x07
  32. mov byte [es:0x18],':'
  33. mov byte [es:0x19],0x07
  34. mov ax,number ;取得标号number的偏移地址
  35. mov bx,10
  36. ;设置数据段的基地址
  37. mov cx,cs
  38. mov ds,cx
  39. ;求个位上的数字
  40. mov dx,0
  41. div bx ;商在ax,余数在dx
  42. mov [0x7c00+number+0x00],dl ;保存个位上的数字
  43. ;求十位上的数字
  44. xor dx,dx
  45. div bx
  46. mov [0x7c00+number+0x01],dl ;保存十位上的数字
  47. ;求百位上的数字
  48. xor dx,dx
  49. div bx
  50. mov [0x7c00+number+0x02],dl ;保存百位上的数字
  51. ;求千位上的数字
  52. xor dx,dx
  53. div bx
  54. mov [0x7c00+number+0x03],dl ;保存千位上的数字
  55. ;求万位上的数字
  56. xor dx,dx
  57. div bx
  58. mov [0x7c00+number+0x04],dl ;保存万位上的数字
  59. ;以下用十进制显示标号的偏移地址
  60. mov al,[0x7c00+number+0x04]
  61. add al,0x30
  62. mov [es:0x1a],al
  63. mov byte [es:0x1b],0x04
  64. mov al,[0x7c00+number+0x03]
  65. add al,0x30
  66. mov [es:0x1c],al
  67. mov byte [es:0x1d],0x04
  68. mov al,[0x7c00+number+0x02]
  69. add al,0x30
  70. mov [es:0x1e],al
  71. mov byte [es:0x1f],0x04
  72. mov al,[0x7c00+number+0x01]
  73. add al,0x30
  74. mov [es:0x20],al
  75. mov byte [es:0x21],0x04
  76. mov al,[0x7c00+number+0x00]
  77. add al,0x30
  78. mov [es:0x22],al
  79. mov byte [es:0x23],0x04
  80. mov byte [es:0x24],'D'
  81. mov byte [es:0x25],0x07
  82. infi: jmp near infi ;无限循环
  83. number db 0,0,0,0,0
  84. times 203 db 0
  85. db 0x55,0xaa

调试例子

用 bochsdbg 调试,start 之后来到一开始的位置,我们直接下个断点到 0x7c00,因为计算机启动后会把主引导程序加载到 0x7c00 处(约定没有为什么):b 0x7c00
然后让 bochs 继续执行到断点处:c
image.png
显示的是接下来要执行的指令,可以通过 s 单步执行
可以通过 r 来查看此时寄存器的值
image.png
首先是这两句,把 es 段寄存器赋值 0xb800,0xB8000~0xBFFFF,留给显卡显示字符

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

使用 sreg 查看段寄存器
image.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,看一下内存中是不是写上了
image.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
image.png
也就是 302 除以 10,商是 30 也就是 0x1e 放在了 ax 中,余数是 2 放在了 dx 中
image.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)

运行结果
image.png

改一下

既然第一次学,肯定得输出一个 hello world 才算圆满,但是这次用个不同的方法,把一堆数据放在开头声明出来
对应书中 c06_mbr.asm 改一下

  1. jmp near start ;因为开头不是代码,所以跳转到代码的位置
  2. mytext db 'H',0x07,'e',0x07,'l',0x07,'l',0x07,'o',0x07,' ',0x07,'W',0x07,\
  3. 'o',0x07,'r',0x07,'l',0x07,'d',0x07,':',0x07
  4. number db 0,0,0,0,0
  5. start:
  6. mov ax,0x7c0 ;设置数据段基地址
  7. mov ds,ax
  8. mov ax,0xb800 ;设置附加段基地址
  9. mov es,ax
  10. cld ;将DF标志位清零,表示正向传送
  11. mov si,mytext ;源地址的偏移地址
  12. mov di,0 ;目标地址的偏移地址
  13. mov cx,(number-mytext)/2 ;实际上等于 12
  14. rep movsw
  15. ;得到标号所代表的偏移地址
  16. mov ax,number
  17. ;计算各个数位
  18. mov bx,ax
  19. mov cx,5 ;循环次数
  20. mov si,10 ;除数
  21. digit:
  22. xor dx,dx
  23. div si
  24. mov [bx],dl ;保存数位
  25. inc bx
  26. loop digit
  27. ;显示各个数位
  28. mov bx,number
  29. mov si,4
  30. show:
  31. mov al,[bx+si]
  32. add al,0x30
  33. mov ah,0x04
  34. mov [es:di],ax
  35. add di,2
  36. dec si
  37. jns show
  38. mov word [es:di],0x0744
  39. jmp near $
  40. times 510-($-$$) db 0
  41. db 0x55,0xaa

在第 18 行,使用了 rep movsw 指令来批量从一个地方复制到另一个地方,它的复制单位是以字为单位
还有个 movsb 是传送以字节为单位,源地址段寄存器用 DS 偏移地址用 SI,目标地址段寄存器用 ES 偏移地址用 DI,要传送单位数用 CX 来确定。每次复制一个单位,SI 和 DI 地址都相应的加一个单位
除此之外还有 DF 标志来确定他是从高地址往低地址复制(置为 1 时)还是从低地址往高地址复制(置为 0 时)

后面循环分解位数也是用 CX 来保存循环的次数,以及 inc 是加一,dec 是减一
image.png