从汇编和寄存器层面,简单描述下方法调用的时候都发生了什么

本文通过一个简单的汇编程序实例,详细解析了汇编语言中方法调用的过程,包括如何使用CALL指令进行方法调用,以及RET指令如何实现方法返回。同时,还介绍了方法调用前后寄存器的变化及栈内存的使用情况。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

        先看一个简单的汇编程序:

assume cs:code,ss:stack
stack segment
	dw 10 dup('a')
stack ends
code segment
start:
	mov ax,stack
	mov ss,ax
	mov sp,20

	mov ax,1
	mov cx,4
	call s
	mov ax,4c00h
	int 21h
  s:
	add ax,ax
	loop s
	ret 
code ends
end start

         自定义一个stack段,然后产生一次方法调用,用ms的debug工具单步调试,可以看到编译后的代码里边s这个标号变成了offset:

-u
0B55:0000 B8530B        MOV     AX,0B53
0B55:0003 8ED0          MOV     SS,AX
0B55:0005 BC1400        MOV     SP,0014
0B55:0008 B80100        MOV     AX,0001
0B55:000B B90400        MOV     CX,0004
0B55:000E E80500        CALL    0016
0B55:0011 B8004C        MOV     AX,4C00
0B55:0014 CD21          INT     21
0B55:0016 03C0          ADD     AX,AX
0B55:0018 E2FC          LOOP    0016
0B55:001A C3            RET

         call s在编译后对应的指令是:

0B55:000E E80500        CALL    0016

         前边表示内存单元,即调试的时候把程序加载到了0b55:0开始的内存区域,而call s对应的机器码在0b55 * 16 + E的地方,E80500表示call s指令编译后的机器码,E8表示call指令,0500是s标号编译后的地址,由于call实际上等同于是call near ptr s,而near编译后是两个字节的值。而这里的5是offset,而不是绝对地址,call 后边的0016是根据call s下一条指令的地址0b55:0011加上这个offset计算出来的,也就是0b55:0016,由于call near ptr s是段内转移,所以段地址不变,即0b55,只显示0011了。

        另外,从内存单元的机器码和其对应的汇编指令看,mov ax stack被编译成了:

0B55:0000 B8530B        MOV     AX,0B53
         即在0b53:0的位置,应该是我们栈的内存区域(为了更明显起见,我用dw 10 dup('a')初始化的栈内存区):
-d 0b53:0
0B53:0000  61 00 61 00 61 00 61 00-61 00 61 00 61 00 61 00   a.a.a.a.a.a.a.a.
0B53:0010  61 00 61 00 00 00 00 00-00 00 00 00 00 00 00 00   a.a.............
0B53:0020  B8 53 0B 8E D0 BC 14 00-B8 01 00 B9 04 00 E8 0D   .S..............
         从0b53:0开始的16个word,word用两个字节存放,高字节存放在高地址,低字节存放在低地址,用word表示的a,是0061,按地址顺序存放后就成了6100了。

        在开始执行前,先看下寄存器的信息:

-r
AX=0000  BX=0000  CX=0043  DX=0000  SP=0000  BP=0000  SI=0000  DI=0000
DS=0B43  ES=0B43  SS=0B53  CS=0B55  IP=0000   NV UP EI PL NZ NA PO NC
        执行前两条条指令后,ss已经指向了0b53:
-t
AX=0B53  BX=0000  CX=0043  DX=0000  SP=0000  BP=0000  SI=0000  DI=0000
DS=0B43  ES=0B43  SS=0B53  CS=0B55  IP=0003   NV UP EI PL NZ NA PO NC
       在执行call s之前,可以看到sp=0014,stack区域的数据却好像被什么改变了:
-t
AX=0001  BX=0000  CX=0004  DX=0000  SP=0014  BP=0000  SI=0000  DI=0000
DS=0B43  ES=0B43  SS=0B53  CS=0B55  IP=000E   NV UP EI PL NZ NA PO NC
-d 0b53:0
0B53:0000  61 00 61 00 61 00 61 00-61 00 53 0B 00 00 08 00   a.a.a.a.a.S.....
0B53:0010  55 0B 57 05 00 00 00 00-00 00 00 00 00 00 00 00   U.W.............
         不过无妨,看看call之后,sp值减小了2,也就是往栈里压入了一个word:
-t
AX=0001  BX=0000  CX=0004  DX=0000  SP=0012  BP=0000  SI=0000  DI=0000
DS=0B43  ES=0B43  SS=0B53  CS=0B55  IP=0016   NV UP EI PL NZ NA PO NC

         这个word的只是多少呢?从stack的内存区域0b53:0013地址可以看到,是0011,其实呢也就是call s下一条指令的地址!由此我们可以知道方法调用的时候,会把方法调用指令后边那条指令的地址压入栈中:

-d 0b53:0
0B53:0000  61 00 61 00 61 00 61 00-01 00 00 00 16 00 55 0B   a.a.a.a.......U.
0B53:0010  57 05 11 00 00 00 00 00-00 00 00 00 00 00 00 00   W...............

         那什么时候会弹出呢?猜猜也是方法调用返回的时候!即ret指令执行的时候,sp增加了2,即弹出了栈里边存的值,并把弹出来的值赋给了ip寄存器。被调用方法里边执行ret指令后的寄存器信息和栈内存区域:

-t
AX=0010  BX=0000  CX=0000  DX=0000  SP=0014  BP=0000  SI=0000  DI=0000
DS=0B43  ES=0B43  SS=0B53  CS=0B55  IP=0011   NV UP EI PL NZ AC PO NC
-d 0b53:0
0B53:0000  61 00 61 00 61 00 61 00-10 00 10 00 00 00 11 00   a.a.a.a.........
0B53:0010  55 0B 57 05 00 00 00 00-00 00 00 00 00 00 00 00   U.W.............

         ret后,从ip指向的指令地址继续执行,也即方法调用指令的下一条指令继续开始。但是不得不说栈内存区域有点紊乱啊,不知道为啥......

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值