摘要
通过编写一个分解整数并显示的一个程序,来学习一些汇编的基础指令和语法
主引导扇区
处理器在加电或者复位以后,如果硬盘是首选的启动设备,那么ROM-BIOS将试图读取硬盘的0面0道1扇区。这就是主引导扇区,会先把主引导扇区的代码(指令集)加载到内存,然后处理器来执行,一般主引导扇区是用来启动操作系统的。但此次,我们把编写的程序加载到主引导扇区,让它被处理器执行。
程序的基本思想
怎么让屏幕上显示字符
为了显示文字,主要是依靠显示器和显卡,显卡里面有一个存储器,通常称为显存。显存是用来告诉显示器应该显示什么字符。显示器的最小单位是像素。
一开始想的是将显存里面的二进制数和像素对应起来,比如1对应亮,0对应不亮(也就是黑),但这只能黑白形式,现在都是彩色的了,所以就用到了3个字节来表示颜色。
另一个问题是,将二进制数和像素一 一 对应起来,是很麻烦的,这里引入了ASCII码,我们不需要想显存里面存的数怎么和像素联系起来,只需要在显存里面写入这个字符的ASCII码,就可以在屏幕上显示出来了(这中间的一些技术细节,就是字符发生器和控制电路的事情了)
我们知道,显存是在显卡上面的,处理器需要访问显卡,才能让字符显示出来,这个过程有点麻烦了,效率不高,所以想到直接让显存映射到内存里面,内存里面专门划出一片空间来和显存映射。一般0xB8000-0XBFFFF这段物理空间是留给显卡的。
怎么保存分解的那些数
直接用寄存器来保存分解的数显然是不太好的,一个是寄存器的数量有限,另一个是寄存器还有它别的任务得做,所以想到直接在内存里面开辟出一段连续的空间,这些空间来放被分解的数。
怎么显示那些被分解的数位
很简单,因为0X8000-0XBFFFF是和显存形成映射的,直接把那些分解的数用mov指令移到那片内存地址就OK了。
贴上最终代码及解释
1 ;代码清单5-1 “;”代表注释符
2 ;文件名:c05_mbr.asm
3 ;文件说明:硬盘主引导扇区代码
4 ;创建日期:2011-3-31 21:15
5
6 mov ax,0xB800
7 mov es,ax
6.7行执行后,es段寄存器指向了内存地址0xB800,也就是用来显示字符的那一片地址区域
8
9 ;以下显示字符串"Label offset:"
10 mov byte [es:0x00],‘L’ 这里的’L’也可以换成对应的ASCII 0X4c;[ ]代表是内存地址
11 mov byte [es:0x01],0x07
12 mov byte [es:0x02],‘a’
13 mov byte [es:0x03],0x07
14 mov byte [es:0x04],‘b’
15 mov byte [es:0x05],0x07
16 mov byte [es:0x06],‘e’
17 mov byte [es:0x07],0x07
18 mov byte [es:0x08],‘l’
19 mov byte [es:0x09],0x07
20 mov byte [es:0x0a],’ ’
21 mov byte [es:0x0b],0x07
22 mov byte [es:0x0c],“o”
23 mov byte [es:0x0d],0x07
24 mov byte [es:0x0e],‘f’
25 mov byte [es:0x0f],0x07
26 mov byte [es:0x10],‘f’
27 mov byte [es:0x11],0x07
28 mov byte [es:0x12],‘s’
29 mov byte [es:0x13],0x07
30 mov byte [es:0x14],‘e’
31 mov byte [es:0x15],0x07
32 mov byte [es:0x16],‘t’
33 mov byte [es:0x17],0x07
34 mov byte [es:0x18],’:’
35 mov byte [es:0x19],0x07
36
37 mov ax,number ;取得标号number的汇编地址(也可以说偏移地址)
38 mov bx,10 ;因为分解数要不断除以10,这里10放到了bx寄存器里面作除数用的
39
40 ;设置数据段的基地址
41 mov cx,cs ;41.42作用是让数据段寄存器和代码段寄存器指向同一个段地址
42 mov ds,cx
43
44 ;求个位上的数字
45 mov dx,0
46 div bx
47 mov [0x7c00+number+0x00],dl ;保存个位上的数字,主引导扇区的代码是从0x7C00开始加载的,所以需要加上。
48
49 ;求十位上的数字
50 xor dx,dx ;这里直接把dx,dx归0了,因为是异或运算,而且效率要比mov dx ,0高
51 div bx
52 mov [0x7c00+number+0x01],dl ;保存十位上的数字
53
54 ;求百位上的数字
55 xor dx,dx
56 div bx
57 mov [0x7c00+number+0x02],dl;保存百位上的数字
58
59 ;求千位上的数字
60 xor dx,dx
61 div bx
62 mov [0x7c00+number+0x03],dl ;保存千位上的数字
63
64 ;求万位上的数字
65 xor dx,dx
66 div bx
67 mov [0x7c00+number+0x04],dl ;保存万位上的数字
68
69 ;以下用十进制显示标号的偏移地址
70 mov al,[0x7c00+number+0x04]
71 add al,0x30
72 mov [es:0x1a],al
73 mov byte [es:0x1b],0x04
74
75 mov al,[0x7c00+number+0x03]
76 add al,0x30
77 mov [es:0x1c],al
78 mov byte [es:0x1d],0x04
79
80 mov al,[0x7c00+number+0x02]
81 add al,0x30
82 mov [es:0x1e],al
83 mov byte [es:0x1f],0x04
84
85 mov al,[0x7c00+number+0x01]
86 add al,0x30
87 mov [es:0x20],al
88 mov byte [es:0x21],0x04
89
90 mov al,[0x7c00+number+0x00]
91 add al,0x30
92 mov [es:0x22],al
93 mov byte [es:0x23],0x04
94
95 mov byte [es:0x24],‘D’
96 mov byte [es:0x25],0x07
97
98 infi: jmp near infi ;无限循环
99
100 number db 0,0,0,0,0 ;db是一个伪指令,不会被编译成机器码,用来声明,这里相当于开辟了5个字节的内存空间,然后都初始化为0了方便存储分解之后的数位。然后number是一个标号,可以代表该处的汇编地址,一般被用来引用
101
102 times 203 db 0 ;重复执行203次db 0 命令,相当于开辟了连续的203字节的内存空间且置于0
103 db 0x55,0xaa ;保证主引导扇区的结尾格式
一些指令
mov指令
mov指令用于数据传送
1.mov指令的目的操作数不能是立即数
2.目的操作数和源操作数不允许同时为内存单元
3.目的操作数必须是通用寄存器或者内存单元
传送指令只影响目的操作数的内容,不改变源操作数的内容
比如 mov ah,bh 指令执行后,ah和bh的内容相同
div指令
div是除法指令
第一种类型是 16位的二进制数除以8位二进制数
这种情况下,被除数必须在寄存器AX中,必须事先传到AX里,除数可以由8位通用寄存器或者内存单元提供。指令执行后,商在寄存器AL中,余数在AH中
第二种类型是用32位的二进制数除以16位的二进制数,因为16位寄存器只能放16位的,所以要拆开放,高16位放在DX中,低16位放在AX中,除数可以由16位通用寄存器或者内存单元提供,指令执行 后,商在AX中,余数在DX中
汇编地址
一般是编译时,编译器生成的地址,每个指令都对应一个,是从0开始的,第一个指令对应的汇编地址是0
标号
标号可以代表该指令处对应的汇编地址,一般是在汇编程序里面被引用的。
伪指令
像db dw 这些都是伪指令,也就是说不能被编译成机器码的,编译完成后,自动消失,只在编译时起作用。
DB是用来声明的,DB是声明字节,跟在后面的数都占一个字节。
DW是声明字数据
DD是声明双字数据
DQ是声明四字数据