简介
掌握汇编语言程序设计方法
- 顺序程序结构
- 分支程序结构
- 循环程序结构
重点 - 控制转移指令
- 转移指令的寻址方式
编制汇编语言程序的步骤
分析问题,确定算法
- 找出合理的算法及适当的数据结构
根据算法画出程序框图
- 由粗到细把算法逐步地具体化
根据框图编写源程序
上机调试
1.顺序程序结构
无分支、无循环、无转移,按照 程序书写的先后顺序以直线方式 单条顺序执行
控制机制
- CS:主存中代码段的段基地址
- EIP:将要执行指令的偏移地址
- 处理器自动增量EIP
设计要点
- 如何选择简单有效的算法
- 如何选择存储单元和工作单元
由于顺序程序结构就是一条线的往下执行,所以很简单,举几个例子也就明白了。
例1.1 自然数求和程序
- 1+2+3+…+N=?
- 等差数列求和:(1+N)*N/2
.data
num dword 3456
sum qword ?
.code
mov eax,num
add eax,1
mul num
shr edx,1 //无符号数,进行逻辑移位
rcr eax,1 //带进位的循环移位,保证edx的最低位移位到eax的最高位
mov dword ptr sum,eax
mov dword ptr sum + 4,edx
例1.2 显示CPU的Vendor信息
CPUID指令:EAX=0,获取CPU的供应商信息,返回结果存储在EBX,EDX,ECX寄存器
include io32.inc
.data
buffer byte 'The processor vendor ID is ',12 dup(0),0
buffersize = sizeof buffer
.code
start:
mov eax,0
cpuid
mov dword ptr buffer+buffersize - 13,ebx
mov dword ptr buffer+buffersize - 9,edx
mov dword ptr buffer+buffersize - 5,ecx
mov eax,offset buffer
call dispmsg
exit 0
end start
例1.3 复杂的表达式计算
编写程序完成表达式R=((XY+5)+4X)/Z的计算
X,Y,Z 均为DWORD类型的变量
Mov eax,X
Imul Y
add eax,5
Adc edx,0
Mov ebx,eax
Mov ecx,edx
Mov eax,X
mov esi,4
imul esi
add eax,ebx
Adc edx,ecx
Idiv Z
Mov R,eax
Mov R+4,edx
2.分支程序结构
目标:
- 理解分支结构的机器实现
- 掌握判断条件的实现与转移指令
- 了解分支结构优化方法
要点
- 条件的实现
- 条件流程控制
- 转移寻址和寻址方式
高级语言与汇编语言的if结构对比
高级语言
- 采用if语句,并根据条件表达式的 结果转向不同的程序分支
汇编语言
无明确的高级逻辑结构硬指令,通 过比较和跳转指令组合实现
- 用比较cmp、and或sub操作影 响(修改)状态标志
- 用条件转移指令判断标志位, 并产生到新地址的分支(跳转到 新地址)
if (op1==0) Cmp op1,0
{ Je L1
x=1; y=2; mov x,0
} mov y,0
else jmp L2
{ L1:
x=0;y=0 ; mov x,1
} mov y,2
L2:
常用的影响状态标志的指令
逻辑指令
- AND,OR,NOT,XOT,TEST
位运算
- SHL,SHR,SAL,SAR,ROL,ROR,RCL,RCR
算数运算指令
- ADD,ADC,SUB,SBB,CMP
CPU状态标志
CMP指令
格式:
- CMP dest, src
影响标志:
- 溢出OF,符号SF,零ZF,进位CF,辅助进位AF,奇偶PF
转移指令
流程控制机制:
- 代码段寄存器CS指示代码段基地址
- 指令指针寄存器EIP指示将要执行指令的偏移地址
- 顺序执行,根据指令字节长度增加EIP
- 分支执行,根据目的地址修改EIP或CS
分类
- 转移范围:段内、段间
- 寻址方式:相对、直接、间接
1. 转移范围
段内转移:当前代码段范围内的转移 - 只改变EIP(偏移地址)
- 近转移(Near)NEAR32,NEAR16
- 短转移(Short)转移范围在127~-128字节
段间转移:不同代码段之间的转移
- 更改CS(段地址)和EIP(偏移地址)
- 远转移(Far) FAR32 ,FAR16
- 32位线性地址空间,48位远转移FAR32
- 实地址存储模型,32位远转移FAR16
2. 指令寻址方式
确定下一条指令的方法,操作数的形式
相对寻址方式(段内转移)
- 指令代码提供目标地址相对于当前EIP的位移量
- 操作数(位移量)=目标地址 - 当前EIP
- 目标地址(新EIP值)= EIP + 位移量
直接寻址方式(段间转移)
- 指令代码直接提供目标地址
- 操作数 = 目标地址
- 新的CS:EIP = 操作数高地址字:操作数低地址双字
间接寻址方式
- 指令代码指示寄存器或存储单元,目标地址来自寄存器或存储单元
- 操作数 = reg / mem
- 目标地址 = [reg] /[mem]
2.1 无条件转移指令JMP
程序无条件改变执行顺序,相当于C/C++的goto
JMP指令的段内寻址方式:
- 段内转移、相对寻址
标号指明目标地址,指令代码包含位移量
JMP label ;JMP newaddr
EIP ← EIP+位移量 – - 段内转移、间接寻址
通用寄存器或主存单元包含目标指令偏移地址
JMP reg32/reg16 ;JMP ebx
EIP ←reg32/reg16
JMP mem32/mem16 ;JMP near ptr [ebx]
EIP ←[mem32]/[mem16] - 段间转移、直接寻址
标号提供所在段的段选择器和偏移地址
JMP label
EIP = label的偏移地址
CS=label的段选择器 - 段间转移、间接寻址
32位线性地址空间用3字存储单元包含目标地址
JMP m48 ;JMP far ptr [ebx]
EIP = mem48
CS = mem48+4
16位实地址用双字存储单元包含目标地址
JMP mem32 ;JMP far ptr [ebx]
EIP = mem32
CS = mem32+2
MASM会根据存储模式等信息自动识别
平展存储模式常用格式
1.相对寻址
– JMP label
- JMP near ptr label
- JMP label
2.寄存器间接寻址
– JMP reg32
- JMP ebx
3.存储器间接寻址
– JMP mem32
- JMP near ptr [ebx]
JMP相对寻址方式
格式:JMP label 操作:EIP =EIP+位移量
位移量=label(目标地址)相对于当前EIP的字节数
范围:
- 近转移(Near) 范围32位有符号数
- 短转移(Short)范围 8位有符号数
include io32.inc
.data
.code
start:
mov eax,5
cmp eax,0
jz L1
add ebx,10
jmp near ptr L2
L1:
sub ebx,10
L2:
exit 0
end start
无条件转移程序
.data
nvar dword ?
.code
start:
jmp labl1 //相对寻址
nop
labl1:
jmp near ptr labl2
nop
labl2:
mov eax,offset labl3
jmp eax //寄存器间接寻址
nop
labl3:
mov eax,offset labl4
mov nvar,eax
jmp nvar //存储器简介寻址
nop
labl4:
exit 0
end start
2.2 条件转移指令
不影响标志,利用标志
当状态标志条件为真时,转移到目标地址;否则,顺序 执行下一条指令
格式: Jcc label
label:目标地址,段内相对寻址
- 32位IA32处理器, 32位的全偏移量
cc:一个或多个标志位的标志位条件
- JC, JNC,JZ,JNZ,…
include io32.inc
.code
start:
mov ebx,5
cmp eax,0
jz L1
add ebx,10
jmp L2
L1:
sub ebx,10
L2:
exit 0
end start
条件转移指令分类
1.单状态标志类
基于特定标志位的值(单标志位)
- JZ,JC,JP,JO,JS
- JNZ,JNC,JNP,JNO,JNS
基于相等性( ZF位)
- JE,JNE
2.组合状态标志类
无符号数的比较(ZF判断相对,CF位判断大小)
- JA,JB,JNA,JNB,JAE,JBE…
有符号数的比较(ZF判断相对,SF和OF位判断大小)
- JG,JL,JNG,JNL,…
例 个数折半程序
将某数组分成元素个数相当的两部分.
Mov eax,lengthof m1
shr eax,1
jnc is_even jc is_odd
jmp is_even
add eax,1 Is_odd:
add eax,1
Is_even:
call dispuid
优化
分支程序是影响程序性能的重要因素之一
Mov eax,lengthof m1
shr eax,1
adc eax,0
call dispuid
当数组长度达到最大(eax=0ffffffffh)会怎样?
例 位测试程序
输入参数eax=1的cpuid指令可以获取CPU特性参数
返回参数edx的bit18代表是否支持PSN(Processor Serial Number)功能,1支持,0不支持
include io32.inc
.data
yes_msg byte 'PSN supported:Yes',0
no_msg byte 'PSN supported:No',0
.code
start:
mov eax,1
cpuid
test edx,040000h
mov edx,offset no_msg
jz disp
mov eax,offset yse_msg
disp:
call dispmsg
exit 0
end start
2.3 单分支结构
类似于高级语言的if – then结构语句
- 当条件满足,发生转移,跳过分支体
- 条件不满足,顺序向下执行分支体
要点:与if语句相反
- 转移指令,条件不成立执行分支体
- if语句,条件成立执行分支体
例 求绝对值
例 小写字母转大写字母 ‘a’ = 61h ‘A’ = 41h
2.4 双分支结构
相当于高级语言的if – then - else语句
使用转移指令J(cc)和Jmp实现分支控制
优化为单分支结构
- 预先执行使用频率较高、不影响判断标志的分支
例 显示ebx中的最高位
2.5 多分支程序
分支处理中又有嵌套的分支,具有多个分支走向
- 利用单分支和双分支结构实现多个分支结构
变量地址表程序
复合表达式的实现
C++语言: 汇编语言: 优化:
if (a > b) && (b>c) Mov eax,b Mov eax,b
{ Cmp a,eax Cmp a,eax
x=1; Ja L1 Jbe next
} jmp next Cmp eax,c
L1: Jbe next
Cmp eax,c Mov x,1
Ja L2 Next:
jmp next
L2:
Mov x,1
Next:
3.循环程序结构
组成部分
- 循环初始:为开始循环准备必要的条件,如循环次 数、必要的初始值;
- 循环体:重复执行的程序代码,包括对循环条件的 修改;
- 循环控制:判断循环条件是否成立,决定是否继续 循环
“先判断、后循环”的循环程序结构
- 对应C/C++语言的while语句
“先循环、后判断”的循环程序结构
- 对应C/C++语言的do语句
3.1 循环指令
例 数组求和程序
//循环初始
mov ecx,lengthof array //ECX = 数组元素个数
xor eax,eax //求和初值为0
mov ebx,eax //数组指针为0
//循环体
again:
add eax,array[ebx*(type array)] //求和
inc ebx //指向下一个数组元素
//循环控制
loop again
mov sum,eax //保存结果
call dispsid //显示结果
3.2 计数控制循环
通过次数控制循环
- 利用LOOP指令属于计数控制
- 常见是“先循环、后判断”循环结构
计数可以减量进行,即减到0结束
计数可以增量进行,即达到规定值结束
例 求最大值程序
3.3 条件控制循环
根据条件决定是否进行循环
- 需要使用有条件转移指令实现
- 多见“先判断、后循环”结构
先行判断的条件控制循环程序
- 很像双分支结构
- 主要分支需要重复执行多次 (JMP的目标位置是循环开始)
- 另一个分支用于跳出这个循环
先行循环的条件控制循环程序
- 类似单分支结构,循环体就是分支体
- 顺序执行就跳出循环
例 字符数字统计程序
.data
string byte 'Do you have fun with Assembly?',0 //以0结尾的字符串
.code
start:
xor ebx,ebx //EBx用来记录字符个数,同事也用于指向字符的指针
again:
mov al,string[ebx]
cmp al,0 //用指令“test al,al”更好
jz done
inc ebx //个数加1
jmp again //继续循环
done:
mov eax,ebx //显示个数
call dispuid
exit 0
end start
3.4 多重循环
实际的应用问题
- 单纯的分支或循环结构
- 循环体中具有分支结构
- 分支体中采用循环结构
- 循环体中嵌套有循环,即形成多重循环结构
如果内外循环之间没有关系
- 比较容易处理
如果需要传递参数或利用相同的数据
- 问题比较复杂
例 冒泡法排序程序
mov ecx,count ;ECX←数组元素个数
dec ecx ;元素个数减1为外循环次数
outlp:
mov edx,ecx ;EDX←内循环次数
mov ebx,offset array
inlp:
mov eax,[ebx] ;取前一个元素
cmp eax,[ebx+1] ;与后一个元素比较
jng next ;前一个不大于后一个,不交换
xchg eax,[ebx+1] ;否则,进行交换
mov [ebx],eax
next:
inc ebx ;下一对元素
dec edx
jnz inlp ;内循环尾
loop outlp ;外循环尾