1、课程目标
- 掌握汇编中数组的定义和访问方式
- 理解串指令的工作原理
- 学会REP前缀的使用
- 掌握内存块操作技术
2、名词解释
| 术语 |
英文 |
说明 |
| 串指令 |
String Instructions |
用于批量处理连续内存数据的指令 |
| 源索引 |
Source Index (ESI/RSI) |
指向源数据的寄存器 |
| 目标索引 |
Destination Index (EDI/RDI) |
指向目标位置的寄存器 |
| 方向标志 |
Direction Flag (DF) |
控制串操作的方向 |
| REP前缀 |
Repeat Prefix |
重复执行串指令的前缀 |
3、使用工具
| 工具 |
用途 |
| MASM |
编写和编译汇编代码 |
| x64dbg |
观察串操作的内存变化 |
4、技术原理
4.1、串指令概览
| 指令 |
说明 |
| MOVSB/MOVSW/MOVSD |
移动字节/字/双字 |
| STOSB/STOSW/STOSD |
存储字节/字/双字 |
| LODSB/LODSW/LODSD |
加载字节/字/双字 |
| SCASB/SCASW/SCASD |
扫描字节/字/双字 |
| CMPSB/CMPSW/CMPSD |
比较字节/字/双字 |
4.2、REP前缀变体
| 前缀 |
条件 |
常用于 |
| REP |
ECX != 0 |
MOVS, STOS, LODS |
| REPE/REPZ |
ECX != 0 且 ZF=1 |
CMPS, SCAS |
| REPNE/REPNZ |
ECX != 0 且 ZF=0 |
CMPS, SCAS |
5、代码实现
5.1、示例1:数组定义与访问
.386
.model flat, stdcall
option casemap:none
.data
; 数组定义
byteArr BYTE 10, 20, 30, 40, 50
wordArr WORD 100, 200, 300, 400
dwordArr DWORD 1000, 2000, 3000, 4000
; 未初始化数组
buffer BYTE 100 DUP(?)
; 初始化为相同值
zeros DWORD 50 DUP(0)
.code
main PROC
; 访问数组元素 - 直接寻址
mov al, byteArr[0] ; 第一个元素
mov al, byteArr[2] ; 第三个元素
; 访问数组元素 - 寄存器间接寻址
mov esi, 0
mov al, byteArr[esi] ; byteArr[0]
; DWORD数组需要乘以4
mov esi, 2 ; 索引2
mov eax, dwordArr[esi*4]; dwordArr[2]
; 遍历数组求和
xor eax, eax ; 累加器清零
mov ecx, 5 ; 数组长度
mov esi, 0 ; 索引
SumLoop:
movzx edx, byteArr[esi] ; 零扩展加载
add eax, edx
inc esi
loop SumLoop ; ECX--, 若ECX!=0则跳转
ret
main ENDP
END main
5.2、示例2:MOVS内存复制
.386
.model flat, stdcall
option casemap:none
.data
source BYTE "Hello World!", 0
dest BYTE 20 DUP(0)
.code
main PROC
; 设置源和目标指针
lea esi, source ; ESI = 源地址
lea edi, dest ; EDI = 目标地址
mov ecx, 13 ; 要复制的字节数
; 清除方向标志(正向:ESI/EDI递增)
cld
; 批量复制
rep movsb ; 重复复制字节
; 或者用MOVSD更高效
lea esi, source
lea edi, dest
mov ecx, 3 ; 12字节 = 3个DWORD
cld
rep movsd ; 每次复制4字节
movsb ; 复制剩余的字节
ret
main ENDP
END main
5.3、示例3:STOS内存填充
.386
.model flat, stdcall
option casemap:none
.data
buffer BYTE 100 DUP(?)
.code
; 内存填充函数 - 类似memset
MemSet PROC dest:DWORD, value:BYTE, count:DWORD
push edi
mov edi, dest ; 目标地址
mov al, value ; 填充值
mov ecx, count ; 字节数
cld ; 正向
rep stosb ; 重复存储
pop edi
ret
MemSet ENDP
; 用DWORD填充更高效
MemSet32 PROC dest:DWORD, value:DWORD, count:DWORD
push edi
mov edi, dest
mov eax, value
mov ecx, count
shr ecx, 2 ; 除以4得到DWORD数
cld
rep stosd ; DWORD填充
; 处理剩余字节
mov ecx, count
and ecx, 3 ; 余数
rep stosb
pop edi
ret
MemSet32 ENDP
main PROC
; 用0xCC填充buffer
push 100
push 0CCh
lea eax, buffer
push eax
call MemSet
ret
main ENDP
END main
5.4、示例4:SCAS字符串查找
#include <stdio.h>
#include <string.h>
// 汇编实现strlen
size_t AsmStrlen(const char* str) {
size_t len;
__asm {
mov edi, str ; 字符串地址
xor al, al ; 查找0(结束符)
mov ecx, -1 ; 最大计数
cld
repne scasb ; 查找直到找到AL
not ecx ; 取反得到实际长度
dec ecx ; 不包括\0
mov len, ecx
}
return len;
}
// 汇编实现strchr
char* AsmStrchr(const char* str, char c) {
char* result;
__asm {
mov edi, str
mov al, c
mov ecx, -1
cld
repne scasb ; 查找字符
jne not_found ; 没找到
dec edi ; EDI指向找到的位置后一位
mov result, edi
jmp done
not_found:
mov result, 0
done:
}
return result;
}
int main() {
const char* str = "Hello, World!";
printf("strlen: %zu\n", AsmStrlen(str));
char* p = AsmStrchr(str, 'W');
if (p) printf("Found 'W' at: %s\n", p);
return 0;
}
5.5、示例5:CMPS字符串比较
#include <stdio.h>
// 汇编实现memcmp
int AsmMemcmp(const void* s1, const void* s2, size_t n) {
int result;
__asm {
mov esi, s1
mov edi, s2
mov ecx, n
cld
repe cmpsb ; 比较直到不相等或结束
je equal
; 不相等,比较最后一个字符
movzx eax, BYTE PTR [esi-1]
movzx edx, BYTE PTR [edi-1]
sub eax, edx
mov result, eax
jmp done
equal:
mov result, 0
done:
}
return result;
}
int main() {
char a[] = "Hello";
char b[] = "Hello";
char c[] = "World";
printf("a vs b: %d\n", AsmMemcmp(a, b, 5)); // 0
printf("a vs c: %d\n", AsmMemcmp(a, c, 5)); // 负数
return 0;
}
6、课后作业
- 基础练习:用汇编实现memcpy函数,尽可能优化性能
- 字符串操作:实现strcmp和strncpy函数
- 数组操作:实现数组求最大值、最小值、平均值
- 逆向操作:编写字符串反转函数,理解方向标志的作用