1、课程目标
- 掌握for/while/do-while的汇编特征
- 学会识别循环的边界条件
- 理解循环优化技术
- 掌握嵌套循环的分析方法
2、名词解释
| 术语 |
英文 |
说明 |
| 循环体 |
Loop Body |
循环执行的主体代码 |
| 循环条件 |
Loop Condition |
继续循环的判断条件 |
| 循环展开 |
Loop Unrolling |
复制循环体减少跳转次数 |
| 循环不变量 |
Loop Invariant |
在循环中不变的值 |
3、使用工具
| 工具 |
用途 |
| IDA Pro |
控制流图识别循环 |
| Ghidra |
反编译还原循环 |
4、技术原理
4.1、循环类型特征
| 类型 |
判断位置 |
特征 |
| while |
循环前 |
可能一次不执行 |
| do-while |
循环后 |
至少执行一次 |
| for |
循环前 |
有明确的初始/条件/增量 |
4.2、汇编模式
while循环:
jmp check
loop_body:
...
check:
cmp/test
jcc loop_body
do-while循环:
loop_body:
...
cmp/test
jcc loop_body
for循环与while类似,但有明确的计数器操作
5、代码实现
5.1、示例1:while循环
int SumWhile(int n) {
int sum = 0;
int i = 1;
while (i <= n) {
sum += i;
i++;
}
return sum;
}
// 反汇编:
// xor eax, eax ; sum = 0
// mov ecx, 1 ; i = 1
// jmp check
// loop_body:
// add eax, ecx ; sum += i
// inc ecx ; i++
// check:
// cmp ecx, [ebp+8] ; i <= n?
// jle loop_body
// ret ; return sum
5.2、示例2:do-while循环
int SumDoWhile(int n) {
int sum = 0;
int i = 1;
do {
sum += i;
i++;
} while (i <= n);
return sum;
}
// 反汇编:
// xor eax, eax ; sum = 0
// mov ecx, 1 ; i = 1
// loop_body:
// add eax, ecx ; sum += i
// inc ecx ; i++
// cmp ecx, [ebp+8] ; i <= n?
// jle loop_body ; 条件在末尾
// ret
5.3、示例3:for循环
int SumFor(int n) {
int sum = 0;
for (int i = 0; i < n; i++) {
sum += i;
}
return sum;
}
// 反汇编:
// xor eax, eax ; sum = 0
// xor ecx, ecx ; i = 0
// jmp check
// loop_body:
// add eax, ecx ; sum += i
// inc ecx ; i++
// check:
// cmp ecx, [ebp+8] ; i < n?
// jl loop_body
// ret
5.4、示例4:嵌套循环
void MatrixFill(int matrix[10][10]) {
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 10; j++) {
matrix[i][j] = i * 10 + j;
}
}
}
// 反汇编特征:
// - 两层跳转结构
// - 外层计数器在内层循环前保存
// - 数组寻址: base + i*40 + j*4
//
// outer_loop:
// xor edi, edi ; j = 0
// inner_loop:
// ; 计算地址并赋值
// inc edi
// cmp edi, 10
// jl inner_loop
// inc esi ; i++
// cmp esi, 10
// jl outer_loop
5.5、示例5:循环展开优化
void ArrayCopy(int* dst, int* src, int count) {
for (int i = 0; i < count; i++) {
dst[i] = src[i];
}
}
// 优化后可能展开为:
// loop:
// mov eax, [esi]
// mov [edi], eax
// mov eax, [esi+4] ; 展开第2次
// mov [edi+4], eax
// mov eax, [esi+8] ; 展开第3次
// mov [edi+8], eax
// mov eax, [esi+12] ; 展开第4次
// mov [edi+12], eax
// add esi, 16
// add edi, 16
// sub ecx, 4
// jnz loop
5.6、示例6:字符串处理循环
int StringLength(const char* s) {
int len = 0;
while (*s != '\0') {
len++;
s++;
}
return len;
}
// 反汇编:
// mov esi, [ebp+8] ; s
// xor eax, eax ; len = 0
// loop:
// movzx ecx, byte [esi] ; *s
// test cl, cl ; == 0?
// jz done
// inc eax ; len++
// inc esi ; s++
// jmp loop
// done:
// ret
5.7、示例7:识别循环类型
/*
识别技巧:
1. while循环:
- 循环开始有jmp跳到条件判断
- 判断在循环体后面
2. do-while循环:
- 没有初始跳转
- 判断在循环体末尾
- 条件跳转回循环开始
3. for循环:
- 有明显的初始化(xor reg, reg 或 mov reg, imm)
- 有明显的增量操作(inc/add)
- 通常有比较和条件跳转
4. 无限循环:
- 无条件判断
- jmp跳回循环开始
*/
// IDA控制流图中的循环:
// - 显示为回边(back edge)
// - 有从下向上的箭头
6、课后作业
- 基础练习:编写三种循环,观察编译结果差异
- 嵌套循环:分析一个双层循环的反汇编
- 优化对比:对比Debug和Release版本的循环
- 实战还原:从反汇编还原一个复杂循环结构