x86 64架构汇编语言

9、数组与串指令

1、课程目标

  1. 掌握汇编中数组的定义和访问方式
  2. 理解串指令的工作原理
  3. 学会REP前缀的使用
  4. 掌握内存块操作技术

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、课后作业

  1. 基础练习:用汇编实现memcpy函数,尽可能优化性能
  2. 字符串操作:实现strcmp和strncpy函数
  3. 数组操作:实现数组求最大值、最小值、平均值
  4. 逆向操作:编写字符串反转函数,理解方向标志的作用