8086 汇编(八)

call指令 和 ret指令

  • call 指令:

    1. 将 call指令 的下一条指令的偏移地址入栈
    2. 跳转到 call指令 定位的目标地址处执行指令
  • ret 指令:

    1. 将栈顶的值 pop 给 IP寄存器,以让代码返回 call指令的下一条指令 处执行
    2. 即将原先入栈的 call指令的下一条指令的偏移地址 赋值给 IP寄存器

    补充:
    1.call指令ret指令 结合起来 ,一般用于函数调用
    2.ret指令 有点类似于高级语言中的 return关键字,但是 ret指令 和 return关键字 还是有区别的。ret指令 的作用是将栈顶的值 pop 给 IP寄存器,以让代码返回到原先 call指令 的下一句处执行。高级语言中 return关键字 的作用:不仅可以返回到调用方的下一句代码处,还可以携带返回值。return关键字是对ret指令的扩展和包装
    3.在底层中(汇编语言中)数据的传递,要么通过寄存器,要么通过内存

  • call 指令使用格式

    call指令格式:
    	call 标号 
    	或者 
    	call 物理地址(地址偏移量)
    标号在经编译器编译后,也会变成地址偏移量
    实际上 call指令 使用的是(cs寄存器:标号)来定位指令地址         
    
    ; xcode演示部分记录下来   
    ; 视频33:21部分  
    ; 视频004 0500 - 08:00
    
  • call 指令使用示例

    assume ds:data, ss:stack, cs:code
    
    ; 数据段         
    data segment
    	db 20 dup(0)  
    	string0 db 'hello world!$'
    data ends
    
    ; 栈段
    stack segment
    	db 20 dup(0) 
    stack ends
    
    ; 代码段
    code segment
    	code_tag: 
    	; 为保险起见,显式设置一下 ds寄存器 和 ss寄存器
    	mov ax, data
    	mov ds, ax
    	mov ax, stack
    	mov ss, ax
    
    	; 使用 call指令 调用屏幕打印功能
    	call PrintFunc     
    
    	; 使用 call指令 调用栈平衡示例
    	call StackBalanceFunc
    
    	mov ax, 1122H  
    
    	; 退出程序
    	mov ah, 4cH
    	int 21H  
    
    	; 使用标号确定功能的位置,相当于定义了一个函数
    	; 打印功能
    	PrintFunc:
    	mov dx, offset string0 	
    	mov ah, 09H
    	int 21H  
    	ret   
    	
    	; 使用标号确定功能的位置,相当于定义了一个函数
    	; 栈平衡功能:
    	StackBalanceFunc:    
    	mov ax, 3344H
    	push ax
    	mov dx, offset string0 	  
    	mov ah, 09H
    	int 21H   
    	pop ax
    	ret    
    code ends        
    
    end code_tag 
    
  • 使用 Call指令 进行函数调用,参数使用寄存器传递

    assume ds:data, ss:stack, cs:code
    
    ; 数据段         
    data segment
    	db 20 dup(0)
    data ends
    
    ; 栈段
    stack segment
    	db 20 dup(0)
    stack ends
    
    ; 代码段
    code segment
    	code_tag: 
    	; 为保险起见,显式设置一下 ds寄存器 和 ss寄存器
    	mov ax, data
    	mov ds, ax
    	mov ax, stack
    	mov ss, ax
    
    	; 业务逻辑代码
    	; 从这里开始调用 SumFunc函数,其中 bx寄存器 和 dx寄存器 保存 SumFunc函数 的形参
    	mov bx, 0003h
    	mov dx, 0004h    
    	call SumFunc
    
    	; 退出程序
    	mov ah, 4cH
    	int 21H  
    
    	; 函数调用传参使用寄存器:
    	; SumFunc函数的参数:传递两个字型(Word)参数,参数分别用 bx寄存器 和 dx寄存器 存放     
    	; SumFunc函数的返回值:返回值存放在 ax寄存器 中
    	; Question:当函数调用时,传递的参数个数很多,导致寄存器不够用的时候,怎么办?
    	; Answer:把参数 Push 入栈进行传递
    	; 函数调用完毕之后,为保持栈平衡,函数内部的局部变量、传递给函数的参数会被 pop 掉
    	; 这就是为什么高级语言中,函数内部的局部变量、传递给函数的参数,我们在函数外面拿不到的原因
    	SumFunc:
    	mov ax, bx
    	add ax, dx
    	ret     
    code ends        
    
    end code_tag 
    
  • 使用 Call指令 进行函数调用,参数使用栈传递(外平栈 - 一般不这么写)

    assume ds:data, ss:stack, cs:code
    
    ; 数据段         
    data segment
    	db 20 dup(0)  
    data ends
    
    ; 栈段
    stack segment
    	db 20 dup(0)
    stack ends
    
    ; 代码段(外平栈 - 一般不这么写)
    code segment
    	code_tag: 
    	; 为保险起见,显式设置一下 ds寄存器 和 ss寄存器
    	mov ax, data
    	mov ds, ax
    	mov ax, stack
    	mov ss, ax
    
    	; 业务逻辑代码
    	; 从这里开始调用 SumFunc函数,将 SumFunc函数 的两个形参入栈
    	push 0003H
    	push 0004H 
    	call SumFunc   
    	; 外平栈:因为上面传参时,push了两次,而且每个栈单元格占2个字节(0004H = 2 * 2; 因此,函数调用结束后,栈顶指针往下移动4字节
    	add sp, 0004H
    	
    	; 退出程序
    	mov ah, 4cH
    	int 21H  
    
    	; 功能函数
    	SumFunc:     
    	mov bp, sp
    	mov ax, ss:[bp + 0004H]	  
    	add ax, ss:[bp + 0002H]             	                
    	ret
    code ends        
    
    end code_tag 
    
  • 使用 Call指令 进行函数调用,参数使用栈传递(内平栈 - 主流写法)

    assume ds:data, ss:stack, cs:code
    
    ; 数据段         
    data segment
    	db 20 dup(0)
    data ends
    
    ; 栈段
    stack segment
    	db 20 dup(0)
    stack ends
    
    ; 代码段(内平栈 - 主流写法)
    code segment
    	code_tag: 
    	; 为保险起见,显式设置一下 ds寄存器 和 ss寄存器
    	mov ax, data
    	mov ds, ax
    	mov ax, stack
    	mov ss, ax
    
    	; 业务逻辑代码
    	push 0003H
    	push 0004H 
    	call SumFunc
    	
    	; 退出程序
    	mov ah, 4cH
    	int 21H  
    	
    	; 函数调用传参使用栈:
    	; SumFunc函数的参数:传递两个字型(Word)参数,参数依次 push 进栈空间
    	; SumFunc函数的返回值:返回值存放在 ax寄存器 中
    	; 注意:
    	; 1.函数内部不能使用 pop 指令取参数,因为会破坏栈平衡。要通过偏移地址来取参数
    	; 2.栈空间中压入了很多数据或者地址,我们肯定希望通过 sp寄存器 来访问这些数据和地址
    	; 	但是SP为栈顶指针,它是不能够随便改动的,所以不允许用下面这种方式来寻址
    	SumFunc_Invalid:
    	mov ax, ss:[sp + 0004H]    ; 实际 8086语法不允许这么写:在8086的寻址里面,sp为栈顶指针寄存器,只能通过 push,pop,add 等方法来更改,不允许直接拿来做运算
    	add ax, ss:[sp + 0002H]    ; 实际 8086语法不允许这么写
    	ret 4 ; 内平栈
    	
    	; 这时候,我们就需要使用 bp寄存器,来代替 sp寄存器(栈顶指针寄存器)
    	; bp寄存器 就是专门用来代替 sp寄存器,用于堆栈里面的寻址
    	; 其实这里把 bp寄存器 换成 其他通用寄存器(ax、bx、cx、dx)也可以
    	; 但是,每个寄存器的设计都是有其特定用途的,bp寄存器 就是专门用来代替 sp寄存器,用以堆栈里面的寻址的,所以建议用 bp寄存器。
    	; 而且,通过编译器编译生成的汇编指令,也都是用标准的 bp寄存器 来代替 sp寄存器(即反汇编的时候,栈段的寻址,都是用的 bp寄存器)  
    	SumFunc:     
    	mov bp, sp
    	mov ax, ss:[bp + 0004H]	  
    	add ax, ss:[bp + 0002H]   
    	; 此时栈顶指针,指向的是 push 0003H 处的地址            
    	; 要保证函数调用前和调用后,栈顶的位置是一样的,所以要恢复栈顶指针的位置
    	; 内平栈,这句指令执行的功能:
    	; 1.将栈顶的值(实际上是call指令的下一条指令的偏移地址) pop 给 IP寄存器
    	; 2.将 SP寄存器的值 + 4,即将 SP寄存器 往下移动移动两个单元格,其中 4 = 2 * 2
    	ret 4 
    code ends        
    
    end code_tag 
    

为什么用栈段来存放函数的参数

Question:为什么是用栈段来存放调用的目标函数的参数,而不是用数据段来存放调用的目标函数的参数?
Answer:
1.当我们使用 call指令,执行函数调用的时候,call指令会先把 call指令的下一条指令的 偏移地址 push 入栈,从此刻开始直到函数执行结束为止,整个栈空间都是在为这个函数服务。当函数执行完毕之后,函数使用栈空间就退出了(恢复到之前的状态了)
2.函数内部在栈空间中取参数的时候,参数的偏移地址是固定的(这里的参数,指的是函数的形参),例如

mov ax, ss:[bp + 0004H]	  
add ax, ss:[bp + 0002H] 

中, 0004H和0002H这两个偏移值是固定的

栈平衡

函数栈平衡:函数在return之前,一定会干一件事情,就是把之前本函数内部 push 进栈的数据,都全部 pop 出栈。这样做是为了保证函数调用前后的栈顶的位置是一致的(防止内存泄漏导致的栈溢出)
函数栈平衡可以通过以下两种方式实现:
1.外平栈:由函数外部保持栈平衡
2.内平栈:由函数内部保持栈平衡
编译器在编译代码的过程中,大部分情况下,都会使用内平栈。
所以在反汇编代码里面看到的,都是以内平栈为主。

可以利用栈平衡的特性来判断函数的调用位置:
比如 IP寄存器的值 突然上升,之后又突然恢复,想都不用想,肯定调用了函数。

在编写iOS代码时,汇编代码是由编译器自动翻译OC代码而生成的。用OC编写函数时,如果在函数内部用到局部变量,那么在编译器将函数编译成汇编代码的时候,就有可能用到栈,编译器就会将局部变量 pop 到当前栈顶指针处。

注意

  • 汇编语言函数调用的返回值,一般放在 ax寄存器 中(这也是为什么高级语言中,一个函数只能有一个返回值的原因)。一个寄存器,可以存放任何类型的返回值,不管是数值,结构体(实质上是地址),还是对象(实质上是地址)。函数不能返回数组,只能返回数组的首地址(注意:NSArray不是一个数组,是一个数组对象)。
  • Question:为什么使用寄存器传参的速度要比使用栈传参的速度要快?
    Answer:如果使用寄存器传参,在CPU内部,一次操作就能完成。如果使用栈传参,CPU还需要通过地址总线,控制总线,数据总线和内存进行交互,需要进行多次操作。虽然在定性上,使用寄存器传参比使用栈传参速度要快,但是在定量上,这对于性能的影响,微乎其微。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值