汇编语言笔记10call和ret指令
汇编语言笔记10call和ret指令
0x00 ret 和retf
ret 指令功能就是pop ip 实现近转移(段内转移), retf 是pop ip 然后pop cs 实现远转移(段间转移)
assume cs:code
stack segment
db 16 dup (0)
stack ends
code segment
mov ax,4c00h
int 21h
start: mov ax,stack
mov ss,ax
mov sp,16
mov ax,0
push ax
mov bx,0
ret
code ends
end start
可以看到执行ret指令后确实跳转到了ip=0 而且栈顶指针sp+2
assume cs:code
stack segment
db 16 dup (0)
stack ends
code segment
mov ax,4c00h
int 21h
start: mov ax,stack
mov ss,ax
mov sp,16
mov ax,0
push cs
push ax
mov bx,0
retf
code ends
end start
同样正确跳转,且sp+4(pop ip pop cs)
很简单 因为ref是pop ip pop cs 那么ip后入栈 cs先如 所以分别是 1000h和0h
0x01 call指令
call指令 作用是 (1)将当前的ip或者 cs和ip压入栈中 (2)转移到指定位置 按照给出转移地址的方式不同,分为以下几种情况讨论:
相对位移地址的call指令
call 标号 进行操作 push ip 并且 jmp near ptr 标号 (所以对应的机器码中保存的是相对位移地址,并且该相对唯一地址时16位 -32768-32767)
结果是6 原因 call s 会让push ip ip是多少呢?执行call s 时ip早已指向下一条指令inc ax 所以 ip是6 然后跳转到s :pop ax那ax 就是6了
绝对地址的call指令
call far ptr 标号 实现的是段间的转移 该指令的作用是 push cs,push ip 然后jmp far ptr 标号
ax数值是1010h 原因是 call far ptr s 那么push cs push ip 所以栈顶sp内容是8,所以 pop ax ax=8,且栈顶是1000, add ax ax ,ax是10h pop bx ,bx是1000, add ax,bx ax是1010h
绝对地址在寄存器
call 16位reg (1)push ip (2)jmp 16位reg
ax 是0bh 因为 call ax 让(ip)=5 (括号寄存器表示寄存器的值)进栈,然后来到6执行 mov bp,sp bp就指向栈顶, add ax,[bp] [bp]的默认段寄存器又是ss 所以[bp]直接指向栈顶元素 5 所以 add ax,5 ax=11 /0bh
绝对地址在内存中的call
(1)call word ptr 内存地址 相当于 push ip,jmp word ptr 内存地址
(2)call dword ptr 内存地址 相当于 push cs push ip jmp dword ptr内存地址
ax中存放的是03h,原因是call word ptr ds:[0eh] 之前栈空 sp指向10h,然后执行该call 那么 push ip 而(ip)指向的是第一条inc ax的偏移地址 那么栈中ss:[0eh]是 该条指令的偏移地址,而ds和ss相同,所以jmp ds:[0eh] 跳转到第一条inc ax 所以三条inc ax全部被执行 ax 为03h (不考虑程序返回的mov ax,4c00h明显不是题目要考的)
ax的值是1 bx的值是0,原因在于,call dword ptr ss:[0],等效于push cs push ip 所以ss:[0eh]保存着cs段地址,而ss:[0ch]保存着call 时的ip指向的偏移地址,由于执行到call时ip已经指向了下一条语句,所以ss:[0ch]保存着nop的偏移地址,最终ax=offset s- nop的偏移地址=1,同理bx=0
0x02 call和ret的配合使用
bx的值是8 call s 让ip也就是mov bx,ax的偏移地址进栈,跳到s 处 s处根据cx的值循环三次,所以ax=8,然后ret pop ip 让指令回到mov bx,ax 所以bx=ax=8
利用栈的特性 我们可以实现子程序的嵌套调用 或者说函数的嵌套调用
assume cs:code,ss:stack
stack segment
dw 8 dup (0)
stack ends
code segment
main: ...
call sub1; 让main函数的ip进栈
...
...
mov ax,4c00h
int 21h
sub1: ...
...
call sub2; 让sub1的ip也进栈
...
ret; 让最上面的ip出栈 也就是回到 sub1原先的指令处
sub2: ...
...
ret; 让最上面的ip出栈 也就是回到 sub1原先的指令处
code ends
end main
0x03 mul指令
mul是乘法指令 跟除法类似,根据位数不同有两种
(1) 两个8位数相乘 ,一个数默认放在AL 另一个放在8位reg或者内存字节单元 结果保存在AX中
(2)两个16位数相乘,一个数默认放在AX,另一个放在16reg或者内存字单元 结果高位放在DX 低位放在AX
计算100*10
mov al,100
mov bl,10;都要为8位寄存器
mul bl
结果如下
计算100*1000=100000= 186a0h 由于1000>255所以都要使用 16位
mov ax,100
mov bx,1000
mul bx
0x04 模块化程序设计
参数的传递
上面利用call 和 ret实现了子程序,但是子程序一般要传递参数和返回值传递,我们可以利用某一个寄存器来传递参数,同样的方法也可以用来传递返回值
比如下面是计算立方的程序:
assume cs:code
data segment
dw 1,2,3,4,5,6,7,8
dd 0,0,0,0,0,0,0,0
data ends
code segment
start: mov ax,data
mov ds,ax
mov si,0
mov di,16
mov cx,8
s: mov bx,[si]
call cube
mov [di],ax
mov [di].2,dx
add si,2
add di,4
loop s
mov ax,4c00h
int 21h
cube: mov ax,bx
mul bx
mul bx
ret
code ends
end start
上面这个程序的立方部分计算是有问题的(是书上的实例) 计算bx的立方,mov ax,bx mul bx 这样计算bx的平方是对的 两个16位相乘 结果低位保存在AX 高位保存在DX 但是再mul bx 就有问题了 只让结果的低位AX与bx相乘,这里是默认了平方的结果高位DX=0 也就是bx小于256了 ,不然结果是错误的
批量数据的传递
上面的cube函数只有一个参数bx 而且返回结果只有两个参数(高16位低16位),如果参数更多该怎么办呢?
(1)如果是类似数组类型的数据 比如字符串 可以将数据批量放到内存中,然后用首地址和长度加上循环的方式 传递参数
(2)通用方法,用栈,可见《附录四》(下午或者明天写附录四的笔记)
两个参数 字符串首地址和长度(循环次数) 长度直接放在cx中 大写函数:
capital:and byte ptr [si],11011111b
inc si
loop capital
ret
完整代码
assume cs:code,ds:data
data segment
db 'conversation'
data ends
code segment
start: mov ax,data
mov ds,ax
mov si,0
mov cx,12
call capital
mov ax,4c00h
int 21h
capital:and byte ptr [si],11011111b
inc si
loop capital
ret
code ends
end start
寄存器冲突的问题
在使用子程序时,有些寄存器很可能在调用之前有特殊的作用,这时候在子程序里再使用就很可能产生冲突,解决方法有(1)使用别的寄存器,但是8086cpu就那么几个寄存器,而且比如cx这种 loop 和 jcxz都要检查的换不了
(2)在子程序开头把所有将要使用的寄存器的值push进栈保存起来,最后退出之前pop出栈恢复
举个例子
capital: mov cl,[si]
mov ch,0
jcxz ok
and byte ptr [si],11011111b
inc si
jmp short capital
ok: ret
完整代码:
assume cs:code
data segment
db 'conversation',0
data ends
code segment
start: mov ax,data
mov ds,ax
mov si,0
call capital
mov ax,4c00h
int 21h
capital:mov cl,[si]
mov ch,0
jcxz ok
and byte ptr [si],11011111b
inc si
jmp short capital
ok: ret
code ends
end start
assume cs:code,ds:data,ss:stack
data segment
db 'word',0
db 'unix',0
db 'wind',0
db 'good',0
data ends
stack segment
db 16 dup (0)
stack ends
code segment
start: mov ax,data
mov ds,ax
mov ax,stack
mov ss,ax
mov sp,10h
mov bx,0
mov cx,4
s: mov si,0
call capital
add bx,5
loop s
mov ax,4c00h
int 21h
capital: push cx
s1: mov cl,[si+bx]
mov ch,0
jcxz ok
and byte ptr [si+bx],11011111b
inc si
jmp short s1
oK: pop cx
ret
code ends
end start
结果正确,主要是cx被循环和jcxz都要使用,冲突就用栈解决