- 记录汇编语言课笔记,可能有不正确的地方,欢迎指出
- 教材《新概念汇编语言》—— 杨季文
- 这篇文章对应书第二章 IA32处理器基本功能 3.3部分
文章目录
- 一、分支程序设计
- (1)分支程序设计示例
- 1. 两种分支结构
- 2. 简单分支示例
- 3. 双分支示例
- (2)无条件和条件转移指令
- 1. 基本概念
- 2. 无条件转移指令
- 3. 条件转移指令
- (3)多分支的实现
一、分支程序设计
(1)分支程序设计示例
1. 两种分支结构
- if结构(图a);if-else结构(图b)
- 需要注意一下用汇编写if-else结构的时候,if分支结束后要用无条件转跳过else分支,后面详细说明
![IA-32汇编语言笔记(8)—— 分支程序设计_第1张图片](http://img.e-com-net.com/image/info8/85b4e49ce2b642d0b562b122e18eb4a2.jpg)
2. 简单分支示例
int cf315(int ch)
{
if(ch>='A' && ch<='Z')
ch+=0x20;
return ch;
}
_asm
{
CF315:
push ebp
mov ebp,esp
cmp DWORD PTR[ebp+8],65
jl SHORT lab1
_asm
{
CF315:
push ebp
mov ebp,esp
mov eax,DWORD PTR[ebp+8]
lea ecx,DWORD PTR[eax-65]
cmp ecx,25
ja lab1
add ecx,32
lab1:
pop ebp
ret
}
- 观察到的优化手段:
- 巧妙地把两个分支减少到一个
- 充分利用寄存器,减少从内存取值
3. 双分支示例
int cf316(int m)
{
m = m & 0x0f;
if(m<10)
m+=0x30;
else
m+=0x37;
return m;
}
_asm
{
CF316:
push ebp
mov ebp,esp
mov eax,DWORD PTR[ebp+8]
and eax,15
mov DWORD PTR[ebp+8],eax
cmp DWORD PTR[ebp+8],10
jge SHORT lab1
mov ecx,DWORD PTR[ebp+8]
add ecx,48
mov DWORD PTR[ebp+8],ecx
jmp lab2
lab1:
mov ecx,DWORD PTR[ebp+8]
add ecx,55
mov DWORD PTR[ebp+8],ecx
lab2:
mov eax,DWORD PTR[ebp+8]
pop ebp
ret
}
_asm
{
push ebp
mov ebp,esp
mov eax,DWORD PTR[ebp+8]
and eax,15
cmp eax,10
jge SHORT lab1
add eax,48
pop ebp
ret
lab1:
add eax,55
pop ebp
ret
}
-
观察:
- 如果不开优化,参数或局部变量都存在堆栈,要修改必须要进行:取到Reg,修改,写回堆栈三步
- 汇编程序是自上往下顺序执行的,(不像C语言中if-else可以选择分支执行;while、for可以循环执行一段代码),它只能用
jcc
指令修改下一条程序代码的位置,就好像不能用if、else、while、for,只能用goto的C程序。因此像if-else结构,必须在if分支最后jmp
跳过else,否则会顺序把else也执行一次。
- 优化手段:
- 用寄存器减少从内存取值
- 避免了jmp指令,两个分支不再合并而是各自返回。减少跳转次数
-
对源程序进行优化
int cf317(int m)
{
m=m&0x0f;
m+=0x30;
if(m>'9')
m+=7;
return m;
}
_asm
{
push ebp
mov ebp,esp
mov eax,DWORD PTR[ebp+8]
and eax,15
add eax,48
cmp eax,57
jle lab1
add eax,7
lab1:
pop ebp
ret
}
- 小结:
- 靠编译器自动优化,再好也是依赖于源C程序的,要想真正提高程序效率,还得从源程序上改进
- 优化策略:
- 减少内存的存取数据,多用寄存器
- 减少跳转数量
- 避免时钟数多的指令(右移代替除法)
- 减少循环次数
- 用内联函数减少call和ret
(2)无条件和条件转移指令
1. 基本概念
段内转移(近转移)
:转移时只重置指令指针寄存器EIP
,不重置代码段寄存器CS
,
段间转移(远转移)
:转移时重置指令指针寄存器EIP
和代码段寄存器CS
- 转移类型判断
转移 |
属于段内还是段间 |
条件转移 |
段内 |
循环指令 |
段内 |
无条件转移 |
段内或段间 |
过程调用和返回 |
段内或段间 |
软中断指令 |
段间 |
中断返回指令 |
段间 |
直接转移
:转移指令中直接给出转移目的地址
间接转移
:转移指令中给出包含转移目的地址的寄存器或存储单元
2. 无条件转移指令
- 无条件段内直接转移
- 无条件段内直接转移指令的机器码格式
![在这里插入图片描述](http://img.e-com-net.com/image/info8/d2141485974e4b23a86a246b3c926ce0.png)
操作码OP
:转移指令的机器码
地址差rel
:转移目标地址偏移(标号LABEL所指定指令的地址偏移)与紧随JMP指令的下一条指令的地址偏移之间的差值。
- rel会被汇编器自动计算,并自动选取为8/16/32位来表示。如果只用了8位,就称为
短(short)转移
。
- 如果程序不能自动计算地址偏差了多少,用32位来表示rel
- 如果编程时可以判断地址偏差不超过8位范围,可以用
SHORT
指令强制汇编器用8位表示rel
- 由于rel是有符号数,转移方向可以向前也可以向后
- 执行无条件段内转移指令时,把指令中的地址差rel加到指令指针寄存器EIP上,使EIP之内容为转移目标地址偏移,从而实现转移。
名称 |
jmp(无条件段内直接转移指令) |
格式 |
jmp label |
动作 |
下一条指令转移到 label 处执行 |
- 无条件段内间接转移
名称 |
jmp(无条件段内间接转移指令) |
格式 |
jmp OPDR |
动作 |
指令使控制无条件地转移到由操作数OPRD的内容给定的目标地址处 |
合法值 |
OPDR:32位寄存器、32位存储单元 |
注意 |
OPRD内容直接被装入指令指针寄存器EIP,从而实现转移 |
3. 条件转移指令
- 之前的文章已经写过,见
jcc
相关部分:IA-32汇编语言笔记(5)—— 控制转移 & 堆栈
- 条件转移指令通过判断状态标志确定转移是否发生,但是本身不影响标志状态
- 也是通过
label
标记确定转移位置,在机器码层面的实现和无条件段内直接转移一样
(3)多分支的实现
int cf319(int x, int operation)
{
int y;
switch (operation) {
case 1:
y = 3 * x;
break;
case 2:
y = 5 * x + 6;
break;
case 4:
case 5:
y = x * x;
break;
case 8:
y = x * x + 4 * x;
break;
default:
y = x;
}
if (y > 1000)
y = 1000;
return y;
}
push ebp
mov ebp, esp
; switch ( operation ) {
mov eax, DWORD PTR [ebp+12] ;取得参数operation(case值)
dec eax ;从0开始计算,所以先减去1
cmp eax, 7 ;从0开始计算,最多就是7
ja SHORT LN2cf319 ;超过,则转default
;
jmp DWORD PTR LN12cf319[ eax*4 ] ;实施多路分支
;
LN6cf319: ; case 1:
; y = 3*x;
mov eax, DWORD PTR [ebp+8]
lea eax, DWORD PTR [eax+eax*2]
jmp SHORT LN7cf319 ; break;
;
LN5cf319: ; case 2:
; y = 5*x+6;
mov eax, DWORD PTR [ebp+8]
lea eax, DWORD PTR [eax+eax*4+6]
jmp SHORT LN7cf319 ; break;
LN4cf319: ; case 4:
; y = x*x ;
mov eax, DWORD PTR [ebp+8]
imul eax, eax
jmp SHORT LN7cf319 ; break;
;
LN3cf319: ; case 8:
; y = x*x+4*x;
mov ecx, DWORD PTR [ebp+8]
lea eax, DWORD PTR [ecx+4]
imul eax, ecx
jmp SHORT LN7cf319 ; break;
LN2cf319: ; default:
; y = x ;
mov eax, DWORD PTR [ebp+8]
; }
LN7cf319: ; if ( y > 1000 )
cmp eax, 1000
jle SHORT LN1cf319
; y = 1000;
mov eax, 1000
LN1cf319: ; return y;
pop ebp ;撤销堆栈框架
ret
;
LN12cf319: ;多向分支目标地址表
DD LN6cf319 ; case 1 (DD代表双字,每4个字节存放一个入口地址)
DD LN5cf319 ; case 2
DD LN2cf319 ; default
DD LN4cf319 ; case 4
DD LN4cf319 ; case 5
DD LN2cf319 ; default
DD LN2cf319 ; default
DD LN3cf319 ; case 8
- 分析
- 最后
LN12cf319
部分,开启了多向分支目标地址表,DD
代表地址表中每一项占4个字节,因此LN12cf319+4*0
就是LN6cf319
;LN12cf319+4*1
就是LN5cf319
,依次类推
jmp DWORD PTR LN12cf319[ eax*4 ]
这句,跳转到地址LN12cf319+eax*4
执行,这是实现switch-case的关键。注意eax要改到0起始
- 空位置
default
必须留出,否则转换过来的时候会出错
- 可以看到地址表是按地址排列的,适用于多分枝且没有大空洞的情况。如果各个case间有大间距,会导致地址表中
default
项过多,最好预处理一下,或者改用if-else逻辑