先看一个简单的汇编程序:
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指向的指令地址继续执行,也即方法调用指令的下一条指令继续开始。但是不得不说栈内存区域有点紊乱啊,不知道为啥......