汇编语言学习(四)
该部分主要包含了王爽《汇编语言》第9章的全部内容和第10章除实验外的内容。
第9章主要介绍了多种jmp指令,其中部分指令根据位移进行转移。需要理解和掌握根据位移进行转移的意义。
而第10章主要介绍了call和ret指令,由call和ret组成了子程序的框架。call和ret也都用到了栈。需要理解call和ret对栈段带来的变化。
1.jmp
1.jmp指令的分类
jmp short s
的机器码中,并未包含标号s的地址,而是包含了转移的位移。
由于CPU执行指令的过程为:
1.从CS:IP指向的内存单元读取指令,读取的指令进入指令缓冲器
2.(IP)=(IP)+所取指令的长度,从而指向下一条指令
3.执行指令,转到1,重复这个过程。
所以转移位移的大小等于 = 标号处的位移 - jmp指令后第一个字节的地址
依据位移转移的意义:
便于程序在内存中的浮动分配。如果指令中包含了转移目的地址,则程序段在内存中的偏移地址就有了严格的限制。
转移指令 | 特点 | 功能 |
---|---|---|
jmp short 标号 |
段内短转移:依据位移进行转移 | (IP)=(IP)+8位位移 |
jmp near ptr 标号 |
段内近转移:依据位移进行转移 | (IP)=(IP)+16位位移 |
jmp far ptr 标号 |
段间(远)转移:转移目的地址在指令中 | (CS:IP)标号在段中地址 |
jmp 16位reg |
后加寄存器 | (IP)=(16位reg) |
jmp word ptr 内存单元地址 |
段内转移:后加内存单元地址 | (IP)等于内存单元数据 |
jmp dword ptr 内存单元地址 |
段间转移:后加内存单元地址 | (CS:IP)等于内存单元数据 |
jcxz 标号 |
短转移,依据位移,cx不为0向下执行 | cx=0,则(IP)=(IP)+8位位移 |
loop 标号 |
短转移,依据位移进行转移 | cx减1,cx不为零跳到标号处执行 |
2.检测点:若使jmp指令执行后,CS:IP指向程序的第一条指令,在data段应该定义哪些数据?
assume cs:code,ds:data
data segment
db 0,0,0 ;注意应该为字节型,jmp时取第二个和第三个零作为IP的值
data ends
code segment
code ends
start: mov ax,data
mov ds,ax
mov bx,0
jmp word ptr [bx+1]
end start
3.实验8 分析一个奇怪的程序,思考为何是这种结果?
assume cs:codesg
codesg segment
mov ax,4C00h
int 21h
start:mov ax,0
s: nop
nop
mov di,offset s
mov si,offset s2
mov ax,cs:[si]
mov cs:[di],ax
s0: jmp short s
s1: mov ax,0
int 21h
mov ax,0
s2: jmp short s1
nop
codesg ends
end start
可以看出,该程序在执行时的顺序是:
s -> s0 -> s -> mov ax,4C00h
分析程序可知,s的前两个nop
指令的机器码被s2的jmp short s1
指令的机器码所替代。之所以会出现从s向上跳到程序结束处mov ax,4C00h
,而不是跳到s1
处,是由于jmp short s1
指令的机器码中并没有存储s1
的位置,而是存储了从s2至s1
的相对位移,借助这个相对位移,s跳到了程序结束处。
4.实验9 根据材料编程
原理:内存地址空间中,B8000H~BFFFFH共32KB的空间,为80*25彩色字符模式的显示缓冲区。向这个地址空间写入数据,写入的内容将立即出现在显示器上。
显示缓冲区分为8页,每页4KB,显示器可以显示任意一页的内容。一般情况下,显示器显示第0页的内容。
一行中,一个字符占两个字节的存储空间,低位字节存储字符的ASCII码,高位字节存储字符的属性。
属性字节的格式:
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|
BL | R | G | B | I | R | G | B |
闪烁 | 背景红 | 背景绿 | 背景蓝 | 高亮 | 前景红 | 前景绿 | 前景蓝 |
黑底对应背景色是000,白底是111。
闪烁效果只有在全屏方式下才能看到。
输入过程中:
输入结束后:(截图看不出来闪烁)
用代码编写如下:
assume cs:code,ds:data,ss:stack
data segment
db 'Welcome to masm!'
db 8Ah,0CCh,0F9h
data ends
stack segment
dw 8 dup (0)
stack ends
code segment
start: mov ax,data
mov ds,ax
mov ax,stack
mov ss,ax
mov sp,16
mov ax,0B872h
mov es,ax
mov cx,3
mov si,0 ;ds:[16+si]
s: push cx
mov bx,0 ;ds:[bx+idata]
mov di,0 ;es:[di+idata]
mov cx,16
s1: mov al,ds:[bx]
mov es:[di],al
add di,2
add bx,1
loop s1 ;填ASCII码
mov cx,16
mov di,1
s2: mov al,ds:10h[si]
mov es:[di],al
add di,2
loop s2 ;填字符属性
mov ax,es
add ax,0Ah
mov es,ax
add si,1
pop cx
loop s ;三行
mov ax,4C00h
int 21h
code ends
end start
2.call和ret
1.基本语法
call和ret | 特点 | 功能类似于汇编指令 |
---|---|---|
ret |
用栈中数据修改IP,近转移 | pop IP |
retf |
用栈中数据修改CS和IP,远转移 | pop IP | pop CS |
call 标号 |
转到标号处执行,依据位移转移 | push IP | jmp near ptr 标号 |
call far ptr 标号 |
转到标号处执行,实现段间转移 | push CS|push IP|jump far ptr 标号 |
call 16位reg |
转移地址在寄存器 | push IP | jump 16位reg |
call word ptr 内存地址 |
转移地址在内存 | push IP|jump word ptr 内存 |
call dword ptr 内存地址 |
转移地址在内存 | push CS|push IP|jmp dword ptr 内存 |
2.检测点:下面的程序执行后,ax中的数值为多少?
assume cs:code,ss:stack
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.顺序执行至call word ptr ds:[0EH]
处。已知条件是ds
与ss
相等,而且ss:[0EH]
处值为0。
首先CPU加载该指令,IP指向下一条指令inc ax
,接着CPU执行call指令,将inc ax
的IP地址放入栈中,IP被修改为0。
所以目前CS:IP指向start:mov ax,stack
2.继续顺序执行至call word ptr ds:[0EH]
处。已知条件是ds
与ss
相等,但是ss:[0EH]
处值为inc ax
的IP地址。
CPU加载指令,IP指向下一条指令inc ax
,接着CPU执行call指令,将inc ax
的IP地址放入栈中,IP被修改指向下一句inc ax
。
3.顺序执行,得到(ax) = 3
。
执行步骤的分析中容易出现错误的地方:
1.未注意ds
与ss
相等,无法进行下一步分析。
2.未考虑call指令将下一句的IP地址push到栈中,下一次取出来直接用。
3.模块化程序设计
利用call和ret指令,可以实现多个相互联系、功能独立的子程序。为使:
1.编写调用子程序的程序时不必关心子程序到底使用了哪些寄存器。
2.编写子程序时不必关心调用者使用了哪些寄存器。
3.不会发生寄存器冲突。
确定了编写子程序的标准框架:
子程序开始:子程序使用的寄存器入栈
子程序内容
子程序使用的寄存器出栈
返回(ret、retf)
上一篇: Kotlin 导入 anko 的问题