汇编语言(王爽第三版)检测点10
检测点10.1
补全程序,实现从内存1000:0000处开始执行指令
汇编源代码check10-1.asm
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,1000H
push ax
mov ax,0000H
push ax
retf
mov ax,4c00H
int 21H
code ends
end start
程序分析:
1. retf指令作用(CPU角度):从栈中弹出2个字单元,并修改CS(第二个字)和IP(第一个字);首先它弹出的是IP,其次是CS,故在压栈时,CS的值首先入栈,IP再入栈。
在汇编编程角度,retf实现了远转移。
讲解:在汇编代码这个层次,retf指令作用是修改CS和IP的值,进而使指令从修改后的地址处开始执行。由于它所依赖的是栈中存储的内容,故在压栈过程中要搞清楚入栈的顺序、入栈的值。
2.熟悉ret指令和RETF指令执行的操作。
我们编译链接后,debug跟踪check10-1.exe
-d ss:0
0B66:0000 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
在stack数据区中初始化了16个0,此时它未成为栈结构。直到初始化栈的结构。
执行代码t t(二次)
AX=0B66 BX=0000 CX=0026 DX=0000 SP=0010 BP=0000 SI=0000 DI=0000
DS=0B56 ES=0B56 SS=0B66 CS=0B67 IP=0008 NV UP EI PL NZ NA PO NC
0B67:0008 B80010 MOV AX,1000
-d ss:00
0B66:0000 00 00 00 00 00 00 66 0B-00 00 08 00 67 0B 68 05 ......f.....g.h.
初始化一个栈sp=0010H(16),栈地址:ss=0b66,这里我们发现一些不应该有的数据(不理会它,它是中断的信息)
继续执行代码:
-d ss:0
0B66:0000 00 00 00 00 00 00 10 00-67 0B 68 05 00 00 00 10 ........g.h.....
发现从高位开始存储10 00 00 00四个字节的数据,(体会栈是从高地址向低地址发展的,sp指针从10H减少到了0cH)
此时的CS=0B67 IP=0010
执行retf代码:
我们发现:CS=1000 IP=0000,CS和IP的值改变了。
总结:ret和RETF依赖于栈的结构存储一个程序执行点(IP或CS和IP),当执行这个代码时,可以恢复到这个程序的执行点(将栈中的数据修改IP或CS和IP,使CPU指向新的CS:IP)
检测点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
程序分析:
1.熟悉call指令的操作过程。call指令在执行时,首先push ip(此时ip应为6 ,CPU将要执行的地址),然后jmp 标号,执行标号处的代码,pop ax (弹栈写入ax,(ax)= 6),我们发现inc ax没有执行。
此时使用的栈空间是系统自动创建的。
2.结论:(ax)= 6
检测点10.3
内存地址 机器码 汇编指令 执行后情况
1000:0 b8 00 00 mov ax,0 ax=0,ip指向1000:3
1000:3 9a 09 00 00 10 call far ptr s push cs,push ip,ip指向1000:9
1000:8 40 inc ax
1000:9 58 s:pop ax ax=8h
add ax,ax ax=10h
pop bx bx=1000h
add ax,bx ax=1010h
程序分析:
1.关键还要明白是什么段地址和偏移地址压栈?遇到CALL指令,老样子,首先将当前的CS和IP值压栈,(cs)=1000H(由CALL的机器码得知),此时IP指向了0008H。它们统统压栈(cs先入栈,ip在栈顶)。(ax)=0
2.转移到标号S处继续执行代码,pop ax出栈写入到ax,(IP)先出栈(0008H),(ax)=0008H
3.add ax,ax 0008H+0008H=0010H
4.pop bx 将(cs)=1000H出栈,并写入bx中,(bx)=1000H
5.add ax,bx 0010H+1000H =1010H
6.(ax)=1010H;代码:inc ax没有执行
检测点 10.4
下面的程序执行后,ax中的数值为多少?
内存地址 机器码 汇编指令 执行后情况
1000:0 b8 06 00 mov ax,6 (ax)=6,ip指向1000:3
1000:3 ff d0 call ax push ip(此时IP值为5),ip转移指向1000:6
1000:5 40 inc ax
1000:6 58 mov bp,sp (bp)=(sp)=fffeh
add ax,[bp] (ax)=6+(ss:bp)=6+5=0bh
程序分析:
1. 遇到CALL指令,老样子,(ip)(此时是5),压栈;这里我们可以不管sp是多少(我的debug是0000H),目前我们确定的就是IP的值是5(压栈的数据)。此时栈中有一个字就是0005H。(sp)=(sp)-2 =>>0000H-2=fffeH
2.由于是CALL ax,(ax)=0006H,直接转移到1000:0006处执行,将sp的值赋值给bp。
3.由于bp默认隶属于ss段寄存器,故[bp]指向ss段的物理内存,也就是栈结构的空间,此时栈中就一个字0005H,那么(ss:[bp])=0005H(读取ss栈中的内容)。
4.add ax,[bp] ,0006H+0005H=000BH
5.结果:ax的值是000BH。inc ax指令依然没有执行。
检测点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
mov ax,0
call word ptr ds:[0eh]
inc ax
inc ax
inc ax
mov ax,4c00h
int 21h
code ends
end start
程序分析:
1. 在栈段初始化并定义了8个字,16个字节的空间。是dw指令。
2. 初始化栈,将栈段地址也赋值给了ds;(ss)=(ds),数据段和栈段是同一个内存空间段。
3. 遇到了call,老样子,将(cs)压栈(这个值肯定是call指令后面的那个指令的cs和ip我们可以不关心它,依据你的debug程序)、将(ip)压栈,此时的ip值应该是第一个inc ax的偏移地址。然后jmp到ds:[0eh]内存单元内容作为偏移地址的点执行代码。由于ds和ss都是同一个段,call的是一个字单元,故ds:[0eh]内存单元是指向的一个字(ds:[0eh]~ds:[0fh]内存单元),此单元正好是栈空间栈顶的存储单元,存储着最后压入的ip的值,这个值就是代码inc ax的偏移地址,那么开始执行inc ax
4.执行3次inc ax后, (ax)=3。
5.总结:这个考察了我们对于CALL 内存空间这个指令的熟悉程度,也考察了栈段和数据段在同一段地址下,怎样读出栈空间单元内容。
为什么不让使用debug来逐步调试?因为在执行到CALL指令时,显示ds:[0eh]=065D(在我的环境中debug后),ds:【oeh】中的内容是中断的信息,并不是我们程序所希望的0011H,它一下就jmp到065D的地址去了,而不是cs:11地址执行。
stack段初始的时候定义的全是0,怎么初始化栈段后有数据了?因为中断信息。
(2)下面的程序执行后,ax和bx中的数值为多少?
assume cs:codesg
stack segment
dw 8 dup(0) ;初始化8个双字,16个字的内存作为栈空间
stack ends
codesg segment
start:
mov ax,stack
mov ss,ax
mov sp,10h ;初始化栈顶
mov word ptr ss:[0],offset s ;(ss:[0])=001ah
mov ss:[2],cs ;(ss:[2])=cs
call dword ptr ss:[0] ;cs入栈,ip=19h入栈,转到cs:1ah处执行指令
;(ss:[4])=cs,(ss:[6])=ip
nop
s: mov ax,offset s ;ax=1ah
sub ax,ss:[0ch] ;ax=1ah-(ss:[0ch])=1ah-19h=1
mov bx,cs ;bx=cs=0c5bh
sub bx,ss:[0eh] ;bx=cs-cs=0
mov ax,4c00h
int 21h
codesg ends
end start