这三章都是跳转指令相关的
第9章 跳转指令的原理
-
何为转移指令?有哪几种类型?有什么区别?
a. 修改IP或CS:IP的统称为转移指令
b. 只修改IP是段内转移,如jmp ax,段内转移又有两个子类: 1.短转移,IP修改范围是[-128,127],比如jmp short 段名; 2.近转移,IP修改范围是[-32768,32767]
c. 8086转移指令分为如下几类:1.无条件转移(如jmp); 2.条件转移(如jnz/jz/jna/ja等); 3. 循环指令(如loop); 4. 过程(iret/ret/call); 5.中断(如int 21h等) -
为什么需要近转移和短转移两种类型的指令? 它们之间有何不同?
a. “jmp short 标号” 功能为:(IP)=(IP)+8位位移- 8位位移 = 标号处的地址 - jmp指令后的第一个字节地址
- short指明此处的位移为8位位移
- 8位位移的范围:[-128,127],用补码表示
- 8位位移由编译程序在编译时算出
b. "jmp near ptr 标号"的功能为:(IP)=(IP)+16位位移
- 16位位移=标号处的地址-jmp指令后的第一个字节地址
- near ptr 指明此处为16位位移,进行段内近转移
- 16位位移范围:[-32768,32767],补码表示
- 16位位移由编译程序在编译时算出
-
介绍操作符offset
a. offset s0就可以得到s0段的偏移地址 -
根据图中程序和其机器码介绍jmp short指令流程
assume cs:codesg
codesg segment
start: mov ax,0
mov bx,0
jmp short s
add ax,1
s: inc ax
codesg ends
end start
a. (CS)=0BBDH,(IP)=0006H,CS:IP指向EB03(jmp short s的机器码)
b. 读取指令码EB03进入指令缓冲寄存器
c. (IP)=(IP)+所读取指令的长度=(IP)+2=0008H,CS:IP指向add ax,1
d. CPU执行指令缓冲器中的指令EB03,03就告诉了CPU将IP向后移动3个字节
e. 指令EB03执行后,(IP)=000BH,CS:IP指向inc ax
f. jmp short特点是:汇编编译后的机器码是根据CS:IP相对位置进行转移的(非常重要,如不熟悉详读书P177-180)
-
根据图中程序和其机器码介绍"jmp far ptr 标号"指令流程
a. “jmp far ptr 标号” 实现的是段间转移(远转移),(CS)=标号所在段的段地址,(IP)=标号在段中的偏移地址
b. 根据CS:IP进行跳转 -
介绍转移地址在寄存器/内存中的jmp指令
a. 寄存器指令格式: jmp 16位reg ,功能:(IP)=(16位reg)
b. 内存格式:- jmp word ptr 内存单元地址(段内转移) ;
功能: 从内存单元地址处开始存放一个字,是转移的目的偏移地址 - jmp dword ptr 内存单元地址(段间转移)
功能: 从内存单元地址处开始存放两个字,高地址的字是转移的目的段地址,低地址处是转移的目的偏移地址
(CS)=(内存单元地址+2)
(IP)=(内存单元地址)
- jmp word ptr 内存单元地址(段内转移) ;
-
理解检测点9.1(1)(2),要定义哪些数据,以及定义后为什么可以跳转?
a. 9.1(1)随便定义一个db型的字符作为占位符或"db 16 dup (0)"都可以.
b. 若选择一个占位符的方式,段会向16Bytes对齐,扩展为长度为16的.由于后续内容没有定义,为0.那么jmp word ptr [bx+1]就是相当于jmp cs:0
; 9.1(1)核心代码
data segment
db 16 dup (0)
; 或者nop
data ends
start:
mov ax, data
mov ds, ax
mov bx, 0
jmp word ptr [bx+1]
; 9.2核心代码
data segment
dd 12345678H
data ends
start:
mov ax, data
mov ds, ax
mov bx, 0
mov [bx], bx
mov [bx+2], cs
jmp dword ptr ds:[0]
- 介绍jcxz指令
a. 指令格式: jcxz 标号
b. 当(cx)=0时,(IP)=(IP)+8位位移;
c. 8位位移=标号处地址-jcxz指令后的第一个字节地址; [-128,127], 补码; 编译时算出;
d. 相当于 if ((cx)==0) jmp short 标号;
e. e.g. 找到2000H段中第一个值为0的字节,并将它的偏移地址存在dx中
mov ax, 2000H
mov ds, ax
mov bx, 0
s:
mov cx, [bx]
jcxz ok
inc bx
jmp short s
ok:
mov dx, bx
- 介绍loop指令
a. loop是循环指令,所有循环指令都是短转移,IP范围[-128,127]
b. loop 标号((cx)=(cx)-1,如果(cx)!=0,转到标号处执行)
c. (cx)=(cx)-1; 如果(cx)!=0,(IP)=(IP)+8位位移
d. loop 标号相当于: (cx)–; if((cx)!=0) jmp short 标号;
e. e.g. 找到2000H段中第一个值为0的字节,并将它的偏移地址存在dx中
start:
mov ax, 2000h
mov ds, ax
mov bx, 0
s:
mov cl, [bx]
mov ch, 0
inc cx
inc bx
loop s
ok:
dec bx
mov dx, bx
-
根据位移进行转移有啥意义和优势?
a. 绝对地址转移对地址的值有严格的限制,并且如果程序修改那么绝对地址也可能变
b. 相对位移不存在这个问题
c. 如果jmp short 超出了[-128,127]字节的限制,那么编译器会报错; 同理,jmp near超出也会
d. 而使用绝对地址跳转编译时无法检测,故不会报错,需要编程者来确保跳转的位置正确 -
实验8,好题:分析如下程序,是否可以正确返回
a. 实验8中的程序会把"jmp short s1"拷贝到两个字节的nop中.为什么可以拷贝成功?因为jmp short s1是两个字节
b. 那么它拷贝后,实际的代码是jmp short s1吗?不是.因为jmp short是根据机器码的值进行相对转移的
c. 所以它实际是向前转移 **s1代码段的长度",也就是转移到codesg刚开始的位置
d. 根据上面三点的分析,最后该程序会执行"mov ax,4c00h ; int 21h",故会成功返回 -
实验9,寻址练习
a. bx作为行索引,si作为列索引,其实这两也可以合并在一起,但是影响可读性
assume cs:code
data segment
db 'welcome to masm!'
db 2H, 24H, 71H
data ends
stack segment
db 16 dup (0)
stack ends
code segment
start:
mov ax,data
mov ds,ax
mov bx,6e0h ; (bx) = line index
mov ax,0b800h
mov es,ax
mov cx,3
mov bp,0
s:
push cx
mov si,40h ; (si) = column index
mov cx,16 ; 'welcome to masm!' length=16
mov di,0
copy_str:
mov al,ds:[di]
mov es:[bx][si],al
mov al,ds:[bp][16]
mov es:[bx][si].1,al
add si,2
inc di
loop copy_str
inc bp
add bx, 0A0h
pop cx
loop s
mov ax, 4c00h
int 21h
code ends
end start
第10章 CALL和RET指令
- 介绍ret指令
a. ret指令用栈中的数据,修改IP内容,实现近转移
b. 流程: (IP)=((SS)*16+(SP); (SP)=(SP)+2
c. 相当于: pop IP
mov ax,4c00h
int 21h
start:
mov ax, stack
mov ss, ax
mov sp, 16
mov ax, 0
push ax
ret
- 介绍retf指令
a. retf指令用栈中的数据,修改CS和IP的内容,实现远转移
b. 流程:(IP)=((SS)*16+(SP)) ; (SP)=(SP)+2 ; (CS)=((SS)*16 + (SP)) ; (SP)=(SP)+2
c. 相当于:pop IP ; pop CS
mov ax, 4c00h
int 21h
start:
mov ax, stack
mov ss, ax
mov sp, 16
mov ax, 0
push cs
push ax
retf
-
介绍call指令
a. 将当前的IP或CS:IP压入栈中 ; 转移
b. call指令不能实现短转移, call指令实现的转移方法和jmp指令原理相同 -
根据位移进行转移的call指令
a. call 标号(当前的IP压栈后,转到标号处执行命令)
b. 流程:(SP)=(SP)-2
((SS)*16+(SP))=(IP)
(IP)=(IP)+16位位移 ; 16位位移=标号处的地址-call指令后的第一个字节的地址,范围是[-32768,32767],补码,编译时算出c. 相当于:
push IP
jmp near ptr 标号d. 程序执行后ax是什么?
答案是:6 -
转移的目的地址在指令中的call指令
a. call fat ptr 标号 -> 实现段间转移
b. 流程为:注:(CS)=标号所在段的段地址,(IP)=标号在段中的偏移地址
(SP)=(SP)-2
((SS)*16+(SP))=(CS)
(SP)=(SP)-2
((SS)*16+(SP))=(IP)c. 相当于:
push CS
push IP
jmp far ptr 标号d. 程序执行后ax/bx是什么?
答案是:(ax)=1010,(bx)=1000 -
转移的目的地址在寄存器中的call指令
a. 格式: call 16位reg
b. 功能:(SP)=(SP)-2
((SS)*16+(SP))=(IP)
(IP)=(16位reg)c. 相当于:
push IP
jmp 16位regd. 程序执行后ax是什么?
答案是:(ax)=0bh. 这道题并不好思考,你需要知道,[bp]是ss:[bp],所以sp的值不重要,重要的是ss:[sp]的值是多少.根据该指令的功能,可知此时ss:[sp]=ss:[bp]=5,所以ax存储的值多少就知道了. -
转移的目的地址在内存中的call指令
a. call word ptr 内存单元地址push IP
jmp word ptr 内存单元地址b. 下面的程序执行后(IP)=?,(SP)=?
mov sp,10h mov ax,0123h mov ds:[0],ax call word ptr ds:[0]
(IP)=0123h,(sp)=0eh
c. call dword ptr 内存单元地址
push CS
push IP
jmp dword ptr 内存单元地址d. 下面的程序执行后(CS)=?,(IP)=?,(SP)=?
mov sp, 10h mov ax, 0123h mov ds:[0], ax mov word ptr ds:[2], 0 call dword ptr ds:[0]
(cs)=0,(ip)=0123h,(sp)=0ch
e. 完成下面两题, 这两题考察call指令的原理
第一题: (ax)=3, 但是我没有理解为什么在Debug中单步跟踪的call跳转的位置和理论值不一样
第二题: (ax)=1,(bx)=0
-
call和ret配合使用实现子程序调用
a. P196 10.7 具体例子详细分析 -
如何进行批量数据传递?
a. 通过内存开辟一块空间,将要传递的参数放在空间中,然后子程序去使用
b. 通过push/pop的方式通过栈传递(其实本质上也是内存空间传递)
c. 基本模式:子程序开始:
子程序中使用的寄存器入栈
子程序内容
子程序中使用的寄存器按入栈相反顺序出栈
返回(ret/retf)d. 下面是一个通过内存空间传递参数的例子
data segment
db 'word', 0
db 'unix', 0
db 'wind', 0
db 'good', 0
data ends
code segment
start:
mov ax, data
mov ds, ax
mov bx, 0
mov cx, 4
s:
mov si, bx
mov dx, cx
call capital
add bx, 5
mov cx, dx
loop s
mov ax, 4c00h
int 21h
capital:
push cx
push si
change:
mov cl, [si]
mov ch, 0
jcxz ok
and byte ptr [si], 11011111b
inc si
jmp short change
ok:
pop si
pop cx
ret
code ends
实验10
注意点:实验10的1,2,3要做到模块化,否则课程设计1需要用到1,2,3时,用起来会非常痛苦.
1.
assume cs:code
data segment
db 'Welcome to masm!', 0
data ends
code segment
start:
mov dh, 8
mov dl, 3
mov cl, 2
mov ax, data
mov ds, ax
mov si, 0
call show_str
mov ax, 4c00h
int 21h
; dh=line, dl=column, cl=color
show_str:
push si
push di
push es
push ax
push cx
mov si,0
mov di,0
mov cx,0b800h
mov es,cx
mov cl,dh
mov ch,0
line_offset:
add di,160
loop line_offset
mov cl,dl
column_offset:
add di,2
loop column_offset
pop cx
mov al,cl
mov ch,0
show:
mov cl,ds:[si]
mov es:[di],cl
mov es:[di].1,al
jcxz show_str_end
add di, 2
inc si
jmp show
show_str_end:
pop ax
pop es
pop di
pop si
ret
code ends
end start
assume cs:code
stack segment
dw 16 dup (0)
stack ends
code segment
start:
mov ax, 4242h ; 被除数低16
mov dx, 0fh ; 被除数高16
mov cx, 0ah ; 除数
call divdw
mov ax, 4c00h
int 21h
divdw:
push bx
push si
mov bx, ax ; store ax register in bx
mov ax, dx
mov dx, 0
div cx ; int(h/n)
mov si, ax ; save int(h/n) in si
mov ax, bx
div cx
mov cx, dx
mov dx, si
pop si
pop bx
ret
code ends
end start
计算结果:
模块化子程序是关键,利用push/pop指令做到子程序调用间的寄存器互不干扰
assume cs:code
data segment
db 16 dup (0)
data ends
stack segment
db 16 dup (0)
stack ends
code segment
start:
mov ax,stack
mov ss,ax
mov sp,16
mov ax,12666
mov bx,data
mov ds,bx
mov si,0
call dtoc
mov dh,8
mov dl,3
mov cl,2
call show_str
mov ax,4c00h
int 21h
; ax:the number which convert to string,16 bits
; ds:data buffer
; si: ds offset
; return: set data buffer
dtoc:
push cx
push dx
push bx
push bp
push ax
mov bx,10
mov dx,0
mov bp,0 ; counter of number length
getnrlen:
mov cx,10
call divdw
inc bp
cmp dx,0
jnz getnrlen
cmp ax,0
jnz getnrlen
dtoc0:
pop ax
mov bx,data
dec bp
dtoc1:
mov cx,10
call divdw
add cl,48
mov ds:[bp],cl
dec bp
cmp dx,0
jnz dtoc1
cmp ax,0
jnz dtoc1
pop bp
pop bx
pop dx
pop cx
ret
; (ax): dword data low 16 bits
; (dx): dword data high 16 bits
; (cx): divior
; return: (dx): high 16 bits, (ax): low 16 bits, (cx)=reminder
divdw:
push bx
push si
mov bx, ax ; store ax register in bx
mov ax, dx
mov dx, 0
div cx ; int(h/n)
mov si, ax ; save int(h/n) in si
mov ax, bx
div cx
mov cx, dx
mov dx, si
pop si
pop bx
ret
; data:show string buffer
; dh=line, dl=column, cl=color
show_str:
push si
push di
push es
push ax
push cx
mov si,0
mov di,0
mov cx,0b800h
mov es,cx
mov cl,dh
mov ch,0
line_offset:
add di,160
loop line_offset
mov cl,dl
column_offset:
add di,2
loop column_offset
pop cx
mov al,cl
mov ch,0
show:
mov cl,ds:[si]
mov es:[di],cl
mov es:[di].1,al
jcxz show_str_end
add di, 2
inc si
jmp show
show_str_end:
pop ax
pop es
pop di
pop si
ret
code ends
end start
第11章 标志寄存器
-
标志寄存器作用和结构
a. 存储相关指令的执行结果
b. 为CPU执行相关指令提供行为依据
c. 控制CPU的相关工作方式
-
ZF标志位
a. 记录add/sub/mul/div/inc/or/and等算术或逻辑运算结果,为0时ZF=1,否则=0 -
PF标志位
a. 记录add/sub/mul/div/inc/or/and等算术或逻辑运算后所有bit位中1的个数是偶数时PF=1,否则PF=0 -
SF标志位
a. 符号标志位,SF=1则是负数,=0是非负 -
CF标志位
a. 记录进位(add)或借位(sub)
b. 对无符号数运算有意义的标志位,其中inc/loop不会影响cf位 -
OF标志位
a. 发生溢出,OF=1,否则OF=0
b. 对有符号数运算有意义的标志位 -
adc指令
a. 带cf进位加法指令
b. adc ax,bx => (ax)=(ax)+(bx)+CF -
两个128位数据相加的子程序
a. 128位需要8个字单元
b. 为什么不能将4个inc指令用两个add si/di, 2进行替换? --> 因为inc和loop不影响cf位,但是add影响cf位
add128:
push ax
push cx
push si
push di
sub ax,ax ; clear CF
mov cx,8
s:
mov ax,ds:[si]
adc ax,ds:[di]
mov ds:[si],ax
inc si
inc si
inc di
inc di
loop s
pop di
pop si
pop cx
pop ax
ret
-
sbb指令
a. 带借位减法指令 => sbb ax,bx => (ax)=(ax)-(bx)-CF -
cmp指令
a. 不保存结果,仅根据计算结果对标志寄存器进行设置
b. cmp ax, ax => (ax)-(ax), zf=1, pf=1, sf=0, cf=0, of=0
c. p225 -
检测比较结果的条件转移指令
-
DF标志
a. 控制每次操作后si/di的增减
b. df=0, si/di递增 ; df=1, si/di递减 -
movsb/movsw指令
a. movsb相当于执行了:((es)*16+(di))=((ds)*16+(si))
if df = 0, then (si) = (si) + 1 , (di) = (di) + 1
if df = 1, then (si) = (si) - 1, (di) = (di) - 1 ; movsw 则是 +2/-2b. rep movsb/movsw 根据cx的值重复执行后面的串传送指令,每执行一次movsb指令si和di都会递增或递减指向后一个或前一个单元,相当于:
s: mov sb loop s
c. 递增/递减则有df控制,而cld/std可以对df进行设置:
cld: 将标志寄存器df=0
std: 将标志寄存器df=1d. 逆向传送,从F000:FFFF拷贝最后16个单元到data:000F
mov ax,0f000h
mov ds,ax
mov si,0ffffh
mov ax,data
mov es,ax
mov di,15
mov cx,16
std
rep movsb
-
pushf和popf
a. 将标志寄存器压栈和弹出
b. 压入的栈是ss吗? -
标志寄存器在Debug中的表示
实验11
assume cs:codesg
datasg segment
db "Beginner's All-purpose Symbolic Instruction Code.", 0
datasg ends
codesg segment
begin:
mov ax,datasg
mov ds,ax
mov si,0
call letterc
mov ax,4c00h
int 21h
letterc:
mov cx,0
mov cl,ds:[si]
jcxz lettercend
cmp cx,'a'
jb again
cmp cx,'z'
ja again
and cl,011011111b
mov ds:[si],cl
again:
inc si
jmp letterc
lettercend:
ret
codesg ends
end begin
习题:
11.2/11.4要做,增加对标志寄存器的理解