汇编语言
1. 基础知识
1.1. 汇编语言的3类指令:
1.2. 总线的种类:
1.3. CPU如何控制外设:
1.4. 存储芯片
存储器芯片按照读写属性分为:
存储芯片按照功能分类:
1.5. CPU的寄存器
寄存器名 | 类型 | 位数 | 作用 |
---|---|---|---|
AX | 通用存储器 | 16 | |
AH | 通用存储器 | 8 | |
AL | 通用存储器 | 8 | |
BX | 数据基址寄存器 | 16 | 存放寻址基址,寻址时段地址是DS |
BH | 通用存储器 | 8 | |
BL | 通用存储器 | 8 | |
CX | 通用存储器 | 16 | 循环寄存器,loop时每次-1直到为0 |
CH | 通用存储器 | 8 | |
CL | 通用存储器 | 8 | |
DX | 通用存储器 | 16 | 16位mul指令,结果的高位放在这个寄存器中 |
DH | 通用存储器 | 8 | |
DL | 通用存储器 | 8 | |
SI | 变址寄存器 | 16 | 存放寻址变址,用于串传送指令的源端偏移地址 (source) |
DI | 变址寄存器 | 16 | 存放寻址变址,用于串传送指令的目的端偏移地址 (destination) |
SP | 栈偏移寄存器 | 16 | 栈顶地址的偏移地址 |
BP | 栈基址寻址 | 16 | 只要出现在[...] 中默认段地址就是SS |
IP | 指令偏移寄存器 | 16 | 指令寄存器 |
CS | 段地址寄存器 | 16 | 指令寄存器 |
SS | 段地址寄存器 | 16 | 栈顶地址的段地址 |
DS | 段地址寄存器 | 16 | 内存段地址默认存放位置,也用于串传送指令movsw 的源端段地址 |
ES | 段地址寄存器 | 16 | 用于串传送指令movsw 指令的目的段地址 |
PSW | 16 |
1.7. 汇编指令
指令 | 用法 | 作用 |
---|---|---|
jmp | jmp 段地址:偏移地址 | 将CS 设置成段地址 的值,将IP 寄存器设置成偏移地址 的值 |
jmp 偏移地址 | 将IP 寄存器设置成偏移地址 的值 | |
mov | mov 目标 来源 | 将来源中的值(可以是寄存器、数值、相对于DS寄存器内段地址偏移的内存) 赋给目标寄存器(适用类型同来源) |
add | add 目标寄存器 来源寄存器(数值) | 将目标寄存器 中的值自加来源寄存器 中的值(或者指令中数值) |
end | end 标号 | 指明程序的入口 |
and | and 目标 来源 | 将目标 的值和来源 的值进行与操作,将结果放在目标 中 |
or | or 目标 来源 | 将目标 的值和来源 的值进行或操作,将结果放在目标 中 |
loop | loop 标号 | 判断寄存器cx 的值,如果cx 值不为0则自减1并返回标号 的位置重新执行,所有循环都是短转移if ((cx)!=0)jmp short 标号 |
dw | dw 数据1,数据2… | 定义数据 |
db | db ‘字符串’ | 按照ASCII码定义字符串 |
[bx+idata] | [bx+idata] | 代指寄存器bx 中的值+idata所指向的内存单元 |
CS:[bx+si+idata] | 段地址:[偏移地址] | 段地址显式指定 |
长度指定 | word/byte ptr | 指定操作的长度是字(word )还是字节(byte ) |
dup | dd/dw 重复次数 dup (数据) | 将数据 重复重复次数 次 |
div | div reg/内存单元 | 使用寄存器AX (32位被除数高位放在DX 中,低位放在AX 中,结果也是原高位放余数,低位放商)中的数据除以指令中的寄存器/内存单元 中的值,商存放在AL 中,余数放在AH 中。 |
jmp short | jmp short 标号 | 从当前代码执行位置将IP寄存器置到标号 的位置,这个命令中标号位置距离当前位置的字节数必须在-128~127 之间 |
jmp near ptr | jmp near ptr 标号 | 这个指令和jmp short 类似,不过这个命令的设置距离范围为-32768~32767 也即16位有符号整形 |
jmp far ptr | jmp far ptr 标号 | 与前两个类似,不过这个命令同时修改CS 寄存器和IP 寄存器,实现长距离的转移 |
jmp 段地址:偏移地址 | jmp 段地址:偏移地址 | 同jmp far ptr |
jmp reg | jmp reg | 只修改IP 寄存器中的值为reg 指定的合法寄存器中的值 |
jmp word ptr | jmp word ptr 内存寻址 | 将IP 寄存器设置成内存寻址地址的数据 |
jmp dword ptr | jmp dword ptr 内存寻址 | 同时设置CS =内存寻址+2 ,IP =内存寻址 ;也就是内存寻址指向低字,低字放偏移地址,高字放段地址 |
jcxz | jcxz 标号 | jmp short if cx is zero 也就是if((cx)==0)jmp short 标号 ,带条件判断的短跳转 |
pop | pop reg | 出栈,从栈顶弹出数据到寄存器reg 中,sp+=2 (由此可见,栈底部地址高,顶部位置低) |
push | push reg | 入栈,将寄存器reg 设置为栈顶字 ,同时sp-=2 |
ret | ret | 从SS*16+SP 即栈顶弹出一个字 放入指令寄存器IP |
retf | retf | 从SS*16+SP 弹出两次,分别放于寄存器IP 和CS 中,即将栈顶的两个字 分别作为下一个指令的地址 |
call | call 标号 | 将当前IP 寄存器中地址入栈,然后将IP 寄存器设置为标号 值;等于执行了push IP和jmp near ptr 标号 |
call far ptr 标号 | 先将CS 入栈,然后将IP 入栈,然后同时修改两者为标号 的地址值 | |
call reg | 同call 标号 ,近距离跳转 | |
call word ptr 内存地址 | 同call 标号 ,将当前地址入栈,同时读取内存指令并跳转 | |
call dword 内存地址 | 同call far ptr 标号 ,同时修改指令段地址和偏移地址 | |
mul | mul reg/内存地址 | 乘法指令,如果是8位则另外一个乘数放在AL 寄存器,结果放在AX 寄存器中;如果是16位乘法则乘数放在AX 寄存器,结果低位在AX 中,高位在DX 中 |
adc | adc 操作对象1 操作多想2 | 操作对象1=操作对象1+操作对象2+CF ,利用这个可以对低位加法后高位进行进位加法 |
sbb | sbb 操作对象1 操作对象2 | 操作对象1-操作对象2-CF ,利用标志寄存器进行借位减法 |
cmp | cmp 操作对象1 操作对象2 | 作减法,但不保存结果,根据符号寄存器判断两者大小关系 |
je | je 标号 | zf==1则跳转 |
jne | jne 标号 | zf==0则跳转 |
jb | jb 标号 | cf==1则跳转 |
jnb | jnb 标号 | cf==0则跳转 |
ja | ja 标号 | cf1&&zf0则跳转 |
jna | jna 标号 | cf1||zf1则跳转 |
movsb | rep movsb | rep 指令指明循环cx 次,DF 标志位表明movsb 执行时SI 和DI 是递增执行还是递减执行 |
movsw | rep movsw | 相当于mov ES:[DI] word ptr DS:[SI] 并根据DF标志来标明SI 和DI 是递增2 还是递减2 |
pushf | pushf | 标志寄存器入栈 |
popf | popf | 标志寄存器出栈 |
iret | iret | pop IP;pop CS;popf; 用于中断处理后恢复寄存器状态 |
int | int n | (IP)=(n*4);(CS)=(n*4+2) 触发中断,调用中断处理函数 |
in | in al/ax,port | 访问端口指令,从端口读入数据,从端口port 读入一个字节,寄存器al (8位)或者ax (16位)。port在0255可以使用直接数,如果在25665535之间就需要使用dx 寄存器作为端口号了 |
out | out port,al/ax | 同上,不过方向相反,这个命令是向端口写出 |
shl | shl reg,count | 左移操作,右侧补0,左高右低。移除的最后一个数值(0/1)放在标志位CF 中,位移超过1的时候count 应该被设置成寄存器cl ,为1的时候可以写直接数 |
shr | shr reg,count | 右移操作,左侧补0,同上 |
sti | sti | 设置标志寄存器IF 位为1 |
cli | cli | 设置标志寄存器IF 位为0 |
1.6. 8086CPU的地址
8086CPU使用的是段+偏移
的方式来确定物理地址,地址的计算方式是段地址*16+偏移地址
。由于16为2的4次方,因此其可以空出4位的偏移地址,段地址是16位的地址,总共加起来是20位的物理地址空间。
1.7. 标志寄存器(PSW)
标志名 | 标志作用 | 所在位 |
---|---|---|
ZF | 判断指令计算结果是不是0,如果是0则置1,否则置0 | 6 |
PF | 指令计算结果所有bit位中的1是否为偶数,偶数则置1,否则置0 | 2 |
SF | 符号标志位,记录指令执行后结果是否为负数,如果为负数则置1,否则置0 | 7 |
CF | 进位标志,如果计算结果需要向高位借位则该值为1,否则为0,仅对无符号作用 | 0 |
OF | 溢出标志,超出寄存器表示范围,发生溢出置1,否则置0,仅对有符号作用 | 11 |
DF | 串传送指令movsb 和movsw 结合使用,1表示si、di递减;0表示si、di递增,设置使用std (助记start desend)关闭使用cld (助记close descend) | 10 |
TF | 单步调试中断,CPU在运行完一条指令后如果发现TF标志位为1就会发生内中断 ,中断号为1 ,然后将CS和IP入栈,并跳转至中断向量表1位置的中断处理程序中 | 8 |
IF | 是否可屏蔽中断位,0表示可屏蔽中断,1表示不屏蔽中断,助记(interval force) | 9 |
1.8. CMP指令标志及含义
命令:cmp ax bx
值类型 | ZF | CF/SF | OF | 含义 |
---|---|---|---|---|
无符号 | 1 | - | - | 相等 |
0 | - | - | 不等 | |
- | 1 | - | ax < bx | |
- | 0 | - | ax >= bx | |
0 | 0 | - | ax > bx | |
有符号 | - | 1 | 0 | ax < bx |
- | 1 | 1 | ax > bx | |
- | 0 | 1 | ax < bx | |
- | 0 | 0 | ax >= bx |
1.9. 内中断
1.9.1. 触发情况
- 除法错误,类型码:0
- 单步执行,类型码:1
- 执行into指令,类型码:4
- 执行int指令,类型码:int指令参数
1.9.2. 中断向量表
中断向量表来指示每个中断处理函数的CS
和IP
寄存器的值。放在0000:0000~0000:03FF
这1024字节的单元中,因为CS
和IP
都是16位寄存器(2字节),因此1024字节的中断向量表一共能支持1024/4=256
个中断向量处理函数。
1.9.3. BIOS和DOS中断例程的安装过程
- CPU加点,初始化(CS)=0FFFFH,(IP)=0。0FFFF:0是一个跳转指令,跳转到BIOS的硬件系统检测和初始化程序;
- 初始化程序建立BIOS所支持的中断向量,即登记中断例程入口地址到中断向量表。例程程序固化在ROM中,一直在内存中存在。
- 硬件系统检测和初始化完成后,调用int 19h进行操作系统引导。从此将计算机交由操作系统控制。
- DOS启动后,完成其他工作外,还将它所提供的中断例程装入内存,并建立响应的中断向量。
BIOS和DOS的中断在一张表中,可以通过int n来触发中断。通过寄存器指明子例程的编号,比如:
mov ah, 2 ; 置光标位置
mov bh,0 ; 置0页
mov dh,5 ; DH中放行号
mov dl,12 ; DL中放列号
int 10h ; 触发中断10
上面这个例子的作用就是通过中断设置光标位置。调用的是2号子例程,这个是在ah中指定的,其他参数在另外的寄存器中指定,这是个BIOS的中断。
1.10. 外中断
内中断是CPU内部产生的中断,中断码一般在寄存器中。外中断就是由CPU外部产生的中断,外中断的中断码由数字总线传输至CPU。外中断分为可屏蔽中断和不可屏蔽中断,对应的CPU内部有IF标志位表示是否屏蔽可屏蔽中断;不可屏蔽的外中断在8086CPU中的中断码固定为2
。一般的外中断都是可屏蔽中断,不可屏蔽中断只有在系统发生重大问题的时候才会触发。键盘的数据读入一般使用的就是外中断,由于学习汇编的初衷是能够更好地掌握高级语言,因此这部分就不加细述。
1.11. 直接定址表
数据标号
可以用一个符号
表示数据的位置。这一章讲的是一些利用数据标号
实现键-值
表,以进行数据的索引。属于语言技巧,用处有的,但偏于实践,于认知并无太大增长。