1. 预备知识
-
ret 指令用栈中的数据修改 IP 的内容,从而实现近转移,执行 ret 指令时进行下面两步操作:
- (IP)=((ss)*16+(sp)),将栈顶元素值赋值给寄存器 IP
- (sp)=(sp)+2,栈顶指针增大,相当于出栈,即 pop IP
-
retf 指令用栈中的数据修改 IP 的内容,从而实现远转移,执行 retf 指令时进行下面四步操作:
- (IP)=((ss)*16+(sp)),将栈顶元素值赋值给寄存器 IP
- (sp)=(sp)+2,栈顶指针增大,相当于出栈,即 pop IP
- (CS)=((ss)*16+(sp)),将栈顶元素值赋值给寄存器 SP
- (sp)=(sp)+2,栈顶指针增大,相当于出栈,即 pop CS
-
call 指令格式为 call 标号,执行该指令时进行如下操作:
- (sp)=(sp)-2,((ss)*16+(sp))=(IP),相当于入栈,即 push IP
- (IP)=(IP)+16位位移,位移通过标号处的地址减去指令后的第一个字节地址得到
-
指令 call far ptr 标号实现段间转移,执行该指令时进行如下操作:
- (sp)=(sp)-2,((ss)*16+(sp))=(CS),相当于入栈,即 push CS;(sp)=(sp)-2,((ss)*16+(sp))=(IP),相当于入栈,即 push IP
- (CS)=标号所在段的段地址,(IP)标号所在段的偏移地址
-
指令 call 16 位寄存器,执行该指令时进行如下操作:
- (sp)=(sp)-2,((ss)*16+(sp))=(IP),相当于入栈,即 push IP
- (IP)=(16位寄存器)
-
指令 call word ptr 内存单元地址相当于:
- push IP
- jmp word ptr 内存地址单元,段内转移,目的偏移地址由内存单元给定
-
指令 call dword ptr 内存单元地址相当于:
- push CS
- push IP
- jmp dword ptr 内存单元地址,段间转移,目的段地址由内存单元高地址给出、偏移地址由内存单元低地址给出
2. 检测点 10.1
补全程序,实现从内存 1000:0000 处开始执行指令。
assume cs:code
stack segment
db 16 dup (0)
stack ends
code segment
start:
mov ax,stack
mov ss,ax
mov sp,16 ;初始化一个空栈
mov ax,____
push ax ;入栈,即CS的值
mov ax,____
push ax ;入栈,即IP的值
retf ;修改CS和IP的值,IP为第1次出栈的值,CS为第2次出栈的值
code ends
- 最后一条 retf 指令修改 CS 和 IP 的值,根据预备知识,首先出栈的值将赋值给寄存器 IP,随后出栈的值将赋值给寄存器 CS。
- 所以,第 1 次入栈的值为 CS,根据题意为 1000H;第 2 次入栈的值为 IP,根据题意为 0。代码段部分为:
code segment
start:
mov ax,stack
mov ss,ax
mov sp,16 ;初始化一个空栈
mov ax,1000h
push ax ;入栈,即CS的值
mov ax,0
push ax ;入栈,即IP的值
retf ;修改CS和IP的值,IP为第1次出栈的值,CS为第2次出栈的值
3. 检测点 10.2
下面的程序执行后,ax 中的值为多少?
内存地址 机器码 汇编指令
1000:0 b8 00 00 mov ax,0
1000:3 e8 01 00 call s
1000:6 40 inc ax
1000:7 58 s:pop ax
- 根据预备知识,执行 call s 指令时首先执行类似于 push IP 的操作,再进行转移。所以,执行完 call s 指令后,栈顶存放着寄存器 IP 的值。由于 CPU 读取指令后,寄存器 IP 的值已经发生变化,为下一条指令的偏移地址,即 6。
- 标号 s 处指令的含义是将栈顶元素赋值给 ax,所以寄存器 ax 中的值为 6。
4. 检测点 10.3
下面的程序执行后,ax 中的值为多少?
内存地址 机器码 汇编指令
1000:0 b8 00 00 mov ax,0
1000:3 9A 09 00 00 10 call far ptr s
1000:8 40 inc ax
1000:9 58 s:pop ax
add ax,ax
pop bx
add ax,bx
- call for ptr s 相当于 push CS,push IP,所以此时栈顶元素为 IP 的值,即 8;第二栈顶元素为 CS 的值,即 1000H。
- 标号处 s 的指令,首先栈顶元素赋值给 ax,再执行 add 指令后,ax 的值为 10H;再出栈和执行 add 指令后,寄存器 ax 的值为 1010H。
5. 检测点 10.4
下面的程序执行后,ax 中的数值为多少?
内存地址 机器码 汇编指令
1000:0 b8 00 00 mov ax,6
1000:2 ff d0 call ax
1000:5 40 inc ax
1000:6 mov bp,sp
add ax,[bp]
- call ax 首先执行 push IP 的功能,即将 5 入栈,然后将寄存器 ax 的内容赋值给 IP 实现段内转移,此时 (IP)=6。
- CPU 开始执行 CS:6 处的指令,首先将寄存器 SP 的内容赋值给 BP,后面 add 指令相当于 add ax,[bp]。
- 寄存器 ax 的原值为 6,[bp] 默认使用段地址为寄存器 SS 的内容,即 [bp] 取的是栈顶的元素 5。最终,寄存器 ax 的值为 0BH。
6. 检测点 10.5
(1)下面的程序执行后,ax 的数值为多少?
assume cs:code
stack segment
dw 8 dup (0)
stack ends
code segment
start:
mov ax,stack
mov ss,ax
mov sp,16 ;空栈
mov ds,ax ;寄存器DS指向栈的段地址
mov ax,0
call word ptr ds:[0EH]
;首先执行push IP(下一条inc的偏移地址),此时SP=0EH;再执行jmp word ptr ds:[0EH],目的偏移地址由ds:[0EH]给出
;因为DS和SS指向同一片内存,且(SP)=0EH,所以ds:[0EH]相当于栈顶元素IP的值,即(IP)=(IP),程序顺序执行
inc ax ;ax=1
inc ax ;ax=2
inc ax ;ax=3
code ends
- 代码段的前四条语句声明一个空栈,并且使寄存器 DS 指向栈。
- call word ptr ds:[0EH] 指令首先执行 push IP,将寄存器 IP 的内容入栈,并且 (SP)=(SP)-2=0EH。
- 然后执行 jmp word ptr ds:[0EH] 实现段内转移,目的地址的偏移地址由 ds:[0EH] 给出。由于 DS 也指向栈段,并且 SP=0EH,所以 ds:[0EH] 实际上指的是栈顶元素,即 IP 的值。即 (IP)=(IP),程序没有受到影响,顺序执行。最后,寄存器 ax 的值为 3。
(2)下面的程序执行后,ax 和 bx 中的数值为多少?
assume cs:code
data segment
dw 8 dup (0)
data ends
code segment
start:
mov ax,data
mov ss,ax
mov sp,16 ;空栈,大小为16字节
mov word ptr ss:[0], offset s
;将标号s处的偏移地址赋值到ss:[0]中,即栈的第8个字
mov ss:[2],cs
;将寄存器CS的内容(标号s处的段地址)赋值到ss:[2]中,即栈的第7个字
call dword ptr ss:[0]
;首先执行push CS和push IP(后一指令nop的CS和IP),此时SP=0CH
;再执行jmp dword ptr ss:[0],目的地址由ss:[0]高地址(标号s处指令的段地址)给出、偏移地址由ss:[0]低地址(标号s处指令的偏移地址)给出
;所以,此时的jmp dword ptr ss:[0]的功能是跳转到标号s处执行
nop
s:
mov ax,offset s ;将标号s处的偏移地址赋值到AX
sub ax,ss:[0cH]
;ax=ax-ss:[0cH],ss:[0cH]的值为栈顶元素,即nop指令的偏移地址,即s.IP-nop.IP为nop指令长度,即为1
mov bx,cs
sub bx,ss:[0eH]
;bx=bx-ss:[0eH],ss:[0eH]的值为第二栈顶元素,即nop指令的段地址,即cs-nop.CS,即为0
code ends
end start
- 代码段的前三条语句定义一个空栈,此时 (SP)=16。
- mov word ptr ss:[0],offset s,将标号 s 处指令的偏移地址赋值到栈中 ss:[0]~ss:[1] 两个字节单元处。
- mov ss:[2],cs,将当前指令的段地址赋值到栈中 ss:[2]~ss[3] 两个字节单元处。
- call dword ptr ss:[0],首先执行 push CS 和 push IP,即将后一指令 nop 的段地址和偏移地址入栈,此时 (SP)=0cH;再执行 jmp dword ptr ss:[0],转移后的目的地址由 ss:[0] 单元的高地址决定、偏移地址由 ss:[0] 单元的低地址决定。根据上两条,高地址为 CS 的内容、低地址为 nop.IP 的内容。所以,执行完当前 call 指令后,CS:IP 指向 nop 指令。
- mov ax,offset s,将标号 s 处指令的偏移地址赋值给寄存器 AX;sub ax,ss:[0cH] 相当于 ax=ax-ss:[0cH]=s.IP-nop.IP。由于 nop 指令长度为 1,所以寄存器 ax 的数值为 1。
- 同理,sub bx,ss:[0eH] 相当于 bx=bx-ss:[0eH]=cs-nop.CS,由于段地址始终没有变化,所以寄存器 bx 的数值为 0。
7. 总结
- ret/retf 指令和 call 指令的操作对象都是栈数据。
- call 在 jmp 指令的基础上,首先有一个入栈的过程。如果是段内转移则将 IP 的值入栈,如果是段间转移则将 CS 和 IP 的值入栈。