栈
CPU可以把一段内存当做栈,提供了push和pop指令。push代表进栈,pop代表出栈。利用两个寄存器来指示栈的内存范围,ss寄存器存储着是段地址,sp寄存器存储着是栈顶地址,即偏移地址。
寄存器数量不够的时候,一般使用栈用来暂时存储数据。
一般栈顶是低地址,栈底是高地址,所以push的时候,sp存储的地址会减少,pop的时候,sp存储的地址会增加。可以把栈想象成一个桶,桶底是高地址,接触地面。往里面放东西,栈顶自然离桶底越来越远。
因为CPU并没有指定栈顶和栈底范围,就会出现越界的情况。
编写程序
assume cs:codesg
codesg segment
mov ax,0123H
mov bx,0456H
add ax,bx
add ax,ax
mov ax,4c00H
int 21H
codesg ends
end
分为伪指令和汇编指令,其中伪指令是由编译器执行的,汇编指令由CPU执行的。
通过编译,编译之后还是有伪指令,直到链接后生成exe,才没有伪指令。
可以由运行CS的指向看出,TEST1.exe中的b823是指令的开头。
伪指令
-
assume:假设’用于将代码段与寄存器关联。例如:assume cs:code,将code代码段关联到cs上,也就是cs指向code代码段。
-
segment:代码段。用来定义一个代码段。例如:code segment定义了一个code的代码段,然后以code ends结尾。
-
start:指定程序入口地址。start和end start成对出现
-
offset:取标号的偏移地址。
start: mov ax,offset start ;相当于mov ax, 0 s: mov ax,offset s ;相当于mov ax,3
寻址方式
idata 代表是十六进制数字常量,比如2000H、2200H等。
-
直接寻址
idata
-
寄存器间接寻址
[bx]
-
寄存器相对寻址
用于结构体:
[bx].idata
用于数组:
idata[si]
idata[di]
用于二维数组:
[bx] [idata]
-
基址变址寻址
用于二维数组:
[bx] [si]
-
相对基址变址寻址
用于表格(结构)中的数组项:
[bx].idata[si]
用于二维数组:
idata[bx] [si]
指令数据长度
-
由寄存器指出指令操作的长度
代表操作16位的数据长度,16位又可以叫做字
mov ax,1
代表操作8位的数据长度,8位又可以叫做字节
mov al,1
-
由操作符 X ptr 指明内存单元的长度,X在汇编指令中可以为word或者byte
用word ptr 指明了指令访问的内存单元是一个字单元
mov word ptr ds:[0],1
用byte ptr 指明了指令访问的内存单元是一个字节单元
mov byte ptr ds:[0],1
-
其他方法
有些指令默认了访问的是字单元还是字节单元,比如push指令只进行字操作
push [1000H]
div 指令
-
除数
在寄存器中获取内存单元中
-
被除数
如果被除数是32位,就在dx和ax中存放,dx放高位,ax放低位
如果被除数是16位,就放在ax中
-
结果
如果除数是8位,则al存储商,ah存储余数
如果除数是16位,则ax存储商,dx存储余数
mul 指令
两个相乘的数,要么都是8位,要么都是16位。
-
8位
一个默认放在al中,另一个放在8位的寄存器或者内存字节单元中
相乘的结果默认放在ax中
-
16位
一个默认放在ax中,另一个放在16位寄存器或者内存字单元中
相乘的结果高位放在dx中,低位放在ax中
转移指令
可以修改IP,或者同时修改CS和IP的指令统称为转移指令。
转移行为分类:
-
段内转移:只修改IP,比如 jmp ax
-
短转移
修改范围:-128~127
-
近转移
修改范围:-32768~32767
-
-
段间转移:同时修改CS和IP,比如jmp 1000:0
转移条件分类:
-
无条件转移指令
-
条件转移指令
-
循环指令
-
过程
-
中断
jmp 指令
根据位移进行转移
jmp short 标号,进行的是短转移,不包含具体的偏移地址,只有相对于当前指令偏移地址。
转移的目的地址在指令中
jmp far ptr 标号,进行的是段间转移。
assume cs:codesg
codesg segment
start: mov ax,0
mov bx,0
jmp far ptr s
db 256 dup (0)
s: add ax,1
inc ax
codesg ends
end start
转移地址在寄存器中
jmp ax
转移地址在内存中
jmp word ptr 内存单元地址(段内转移)
mov ax,0123H
mov ds:[0],ax
jmp word ptr ds:[0]
jmp dword ptr 内存单元地址(段间转移)
mov ax,0123H
mov ds:[0],ax
mov word ptr ds:[2],0
jmp dword ptr ds:[0]
jcxz 指令
jcxz是有条件转移指令,所有有条件转移指令都是短转移。
如果cs=0,转移到标号出执行,类似于if语句。
if( (cx) == 0 ) jmp short 标号;
loop 指令
loop指令用于循环执行,loop指令是循环指令,所有的循环指令都是短转移。
( cx ) --;
if( ( cx ) != 0 ) jmp short 标号;
ret 和retf 指令
ret 指令是用栈中的数据,修改IP的内容,从而实现近转移。
pop ip ; 相当于执行该语句
retf 指令是用栈中的数据,修改IP的内容,从而实现远转移。
; 相当于执行以下指令
pop ip
pop cs
call 指令
CPU执行call指令时,进行两步操作:
-
将当前的IP或者CS和IP压入栈
-
转移
call指令,按照我的理解,就是保护好现场,然后再去转移,执行其他指令。
call指令不能实现短转移。
依据位移进行转移
call 标号
; 相当于以下指令
push ip
jmp near ptr 标号
转移的目的地址在指令中
call far ptr 标号
; 相当于以下指令
push cs
push ip
jmp far ptr 标号
转移地址在寄存器中
call ax
; 相当于以下指令
push ip
jmp ax
转移地址在内存中
call word ptr 内存单元地址
;相当于以下指令
push ip
jmp word ptr 内存单元地址
; 举例
mov sp,10h
mov ax,0123h
mov ds:[0],ax
call word ptr ds:[0]
call dword ptr 内存单元地址
;相当于以下指令
push cs
push ip
jmp dword ptr
; 举例
mov sp,10h
mov ax,0123h
mov ds:[0],ax
mov word ptr ds:[2],0
call dword ptr ds:[0]
call 和ret配合使用,可以实现高级语言的函数调用,举例
int fun()
{
return 1; // 相当于ret指令
}
int main()
{
fun();// 相当于call这个函数
return 0;
}