Masm64:串操作指令

1. 字符串定义

在MASM64中,字符串可以定义在数据段(.data)中。字符串本质上是字节序列,可以使用BYTE类型定义。

.data
    str1 BYTE 'Hello, World!',0 ; 以0(空字符)作为字符串的结束标志

2. 字符串长度获取

手动计算长度(不包含结束标志)

可以通过遍历字符串直到遇到结束标志(0)来计算字符串的长度。

str_length PROC
    lea rsi, str1
    mov rcx, 0
    length_loop:
    mov al, [rsi]
    cmp al, 0
    je end_length
    inc rcx
    inc rsi
    jmp length_loop
    end_length:
    ret
str_length ENDP

使用系统函数(如Windows API中的lstrlen函数,在MASM32环境下,可类比在MASM64中的实现思路)

在MASM32中,如果链接了适当的库,可以使用lstrlen函数来获取字符串长度。在MASM64中,如果在Windows环境下,也可以调用类似的函数来获取字符串长度,不过要注意函数的参数传递方式和调用约定。

3. 字符串复制

简单的字节复制(不考虑字符串结束标志)

假设要将一个字符串复制到另一个内存区域,可以使用MOV指令逐个字节进行复制。但这种方法比较原始,容易出错且不考虑字符串的结束标志。

simple_copy PROC
    lea rsi, str1
    lea rdi, destination ; 假设destination是已经定义好的目标内存区域
    mov rcx, 10 ; 假设str1的长度为10(实际应用中应该先获取准确长度)
    copy_loop:
    mov al, [rsi]
    mov [rdi], al
    inc rsi
    inc rdi
    dec rcx
    jnz copy_loop
    ret
simple_copy ENDP

使用标准的字符串复制函数(如strcpy或类似功能的函数)

在C标准库中,有strcpy函数用于字符串复制。在MASM64中,如果要实现类似功能,可以自己编写函数遵循类似的逻辑,即从源字符串中逐个字节复制字符到目标字符串,直到遇到源字符串的结束标志(0)。

my_strcpy PROC
    lea rsi, str1
    lea rdi, destination
    copy_loop2:
    mov al, [rsi]
    mov [rdi], al
    cmp al, 0
    je end_copy
    inc rsi
    inc rdi
    jmp copy_loop2
    end_copy:
    ret
my_strcpy ENDP

4. 字符串比较

逐个字节比较(区分大小写)

可以通过遍历两个字符串,逐个字节比较字符的ASCII值。

str_compare PROC
    lea rsi, str1
    lea rdi, str2
    compare_loop:
    mov al, [rsi]
    mov bl, [rdi]
    cmp al, bl
    jne not_equal
    cmp al, 0
    je equal
    inc rsi
    inc rdi
    jmp compare_loop
    not_equal:
    ; 如果两个字符不相等,设置相应标志或返回比较结果
    equal:
    ; 如果两个字符串完全相等,设置相应标志或返回比较结果
    ret
str_compare ENDP

不区分大小写的比较:要实现不区分大小写的比较,可以在比较之前将字符都转换为大写或小写。例如,将字符转换为大写后再进行比较,通过检查字符是否在a - z范围内,如果是则将其减去32得到大写字母的ASCII值。

case_insensitive_compare PROC
    lea rsi, str1
    lea rdi, str2
    compare_loop2:
    mov al, [rsi]
    mov bl, [rdi]
    ; 将字符转换为大写
    cmp al, 'a'
    jb no_lowercase1
    cmp al, 'z'
    ja no_lowercase1
    sub al, 32
    no_lowercase1:
    cmp bl, 'a'
    jb no_lowercase2
    cmp bl, 'z'
    ja no_lowercase2
    sub bl, 32
    no_lowercase2:
    cmp al, bl
    jne not_equal2
    cmp al, 0
    je equal2
    inc rsi
    inc rdi
    jmp compare_loop2
    not_equal2:
    equal2:
    ret
case_insensitive_compare ENDP

5. 字符串连接

简单的字符串连接(不考虑目标字符串的大小限制)

要将一个字符串连接到另一个字符串的末尾,可以先找到目标字符串的末尾(即找到结束标志0的位置),然后从源字符串的第一个字符开始逐个字节复制到目标字符串末尾之后的位置。

str_concat PROC
    lea rsi, str2
    lea rdi, str1
    ; 找到str1的末尾
    find_end:
    mov al, [rdi]
    cmp al, 0
    jne find_end
    dec rdi
    ; 开始复制str2到str1的末尾之后
    concat_loop:
    mov al, [rsi]
    mov [rdi + 1], al
    cmp al, 0
    je end_concat
    inc rsi
    inc rdi
    jmp concat_loop
    end_concat:
    ret
str_concat ENDP

Masm64:字符串操作指令

1. MOVS(Move String)串传送指令:movsb/movsw/movsd

功能:MOVS指令用于将一个字符串(字节串、字串或双字串等)从源地址移动到目的地址。在64 - bit模式下,它通常操作字节串,默认的源操作数是DS:ESI(数据段中的源索引寄存器指向的地址),目的操作数是ES:EDI(附加段中的目的索引寄存器指向的地址)。

操作示例:假设要将一个字符串从源缓冲区source_buf移动到目的缓冲区dest_buf,缓冲区的偏移地址分别存储在rsi(源)和rdi(目的)寄存器中。

.data
    source_buf BYTE 'Hello, World!', 0
    dest_buf BYTE sizeof source_buf DUP(?)
.code
    mov rsi, offset source_buf
    mov rdi, offset dest_buf
    cld ; 清除方向标志,使地址递增(用于正向移动字符串)
    movsb ; 移动一个字节
    ; 如果要移动整个字符串,可以使用循环结构,
    mov rcx, sizeof source_buf
    rep movsb ; 重复移动字节,直到rcx为0

在 64-bit 模式下,MOVS指令主要支持以下数据类型的字符串操作:

1. 字节串(Byte String):这是最基本的数据类型。每个元素的大小为 1 个字节(8 位)。例如,可以用来操作包含 ASCII 字符的字符串。在 64-bit 模式下,MOVSB(Move String Byte)用于移动单个字节的操作。当需要逐字节地移动字符串时,会使用到这个操作。比如将一个字符串中的每个字节从源地址复制到目的地址。

2. 字串(Word String):字的大小在 x86-64 架构中通常为 2 个字节(16 位)。在这种情况下,MOVSW(Move String Word)用于移动字串。不过在实际的 64-bit 编程中,字串的操作相对较少使用,因为 64 位系统更倾向于处理更大的数据块以提高效率,但在一些特定的场景下,如与 16 位代码或数据交互时,可能会用到字串操作。

3. 双字串(Doubleword String):双字的大小为 4 个字节(32 位)。MOVSD(Move String Doubleword)用于移动双字串。在很多情况下,32 位的数据操作在 64-bit 模式下仍然是有意义的,特别是当处理一些遗留代码或者与 32 位程序或库进行交互时,双字串的操作会被使用到。

综上所述,在 64-bit 模式下的 MOVS指令可以支持字节串、字串和双字串这几种数据类型的字符串操作。但在实际应用中,需要根据具体的需求和场景选择合适的数据类型进行操作。

2. CMPS(Compare String)串比较指令:cmpsb/cmpw/cmpd

功能:CMPS指令用于比较两个字符串(字节串、字串或双字串等)。它比较DS:ESI指向的源字符串和ES:EDI指向的目的字符串的对应元素,比较结果反映在标志位上,例如,如果两个字节相等,则零标志位ZF = 1。

操作示例:比较两个字符串str1和str2:

.data
    str1 BYTE 'Hello', 0
    str2 BYTE 'Hello', 0
.code
    mov rsi, offset str1
    mov rdi, offset str2
    cld ; 清除方向标志,正向比较
    movsb ; 比较一个字节
    ; 如果要比较整个字符串,可以使用循环结构并检查标志位
    mov rcx, sizeof str1
    repz cmpsb ; 重复比较字节,直到rcx为0或者找到不相等的字节(如果zf = 0则停止)
    jz strings_equal ; 如果zf = 1,说明字符串相等,跳转到相应的标签
    ; 如果zf = 0,字符串不相等的处理代码
    jmp strings_not_equal
    strings_equal:
    ; 字符串相等的处理代码
    jmp end_compare
    strings_not_equal:
    ; 字符串不相等的处理代码
    end_compare:

比较字串:假设要比较两个字串word_str1和word_str2。

.data
    word_str1 WORD 1234h, 5678h
    word_str2 WORD 1234h, 5678h
.code
    mov rsi, offset word_str1
    mov rdi, offset word_str2
    cld
    mov rcx, (sizeof word_str1)/2 ; 因为是字串,所以除以2
    repz cmpsw
    jz word_strings_are_equal
    jmp word_strings_are_not_equal
    word_strings_are_equal:
    ; 字串相等的处理代码
    jmp end_word_compare
    word_strings_are_not_equal:
    ; 字串不相等的处理代码
    end_word_compare:

比较双字串:对于双字串dword_str1和dword_str2的比较如下。

.data
    dword_str1 DWORD 12345678h, 9abcdef0h
    dword_str2 DWORD 12345678h, 9abcdef0h
.code
    mov rsi, offset dword_str1
    mov rdi, offset dword_str2
    cld
    mov rcx, (sizeof dword_str1)/4 ; 因为是双字串,所以除以4
    repz cmpsd
    jz dword_strings_are_equal
    jmp dword_strings_are_not_equal
    dword_strings_are_equal:
    ; 双字串相等的处理代码
    jmp end_dword_compare
    dword_strings_are_not_equal:
    ; 双字串不相等的处理代码
    end_dword_compare:

3. SCAS(Scan String)串查找指令:scasb/scasw/scasd

功能:SCAS指令用于在一个字符串中扫描特定的值。它将AL(字节扫描)、AX(字扫描)或EAX(双字扫描)中的值与ES:EDI指向的字符串中的元素进行比较,比较结果也反映在标志位上。

方向标志(DF)对操作的影响:方向标志DF控制EDI寄存器的增减方向。如果DF = 0(通过CLD指令设置),则EDI在每次扫描操作后自动递增,这适用于正向扫描字符串(从低地址向高地址扫描);如果DF = 1(通过STD指令设置),则EDI在每次扫描操作后自动递减,用于反向扫描字符串(从高地址向低地址扫描)。

例如,在上述字节串扫描示例中,如果要反向扫描字符串,可以在scasb指令之前加入std指令,并且在处理扫描结果时需要考虑扫描方向的变化对逻辑的影响。

字节扫描(SCASB)

当使用SCASB指令时,是将AL寄存器中的字节值与ES:EDI指向的字节串中的元素逐个进行比较。例如,在一个字节串中查找特定的ASCII字符。

.data
    search_str BYTE 'This is a test string', 0
.code
    mov rdi, offset search_str
    mov al, 'a'
    cld ; 清除方向标志,正向扫描
    scasb ; 扫描一个字节
    jz character_found ; 如果zf = 1,说明找到了字符,跳转到相应标签
    jmp character_not_found
    character_found:
    ; 找到字符的处理代码
    jmp end_search
    character_not_found:
    ; 未找到字符的处理代码
    end_search:

字扫描(SCASW)

对于SCASW指令,是将AX寄存器中的字值与ES:EDI指向的字串中的元素进行比较。这种情况适用于处理16 - bit的数据元素组成的字符串。不过在64 - bit模式下,字扫描相对较少使用,但在特定的与16 - bit数据交互或者处理遗留代码时可能会用到。

双字扫描(SCASD)

使用SCASD指令时,将EAX寄存器中的双字值与ES:EDI指向的双字串中的元素进行比较。在处理32 - bit数据元素组成的字符串或者与32 - bit程序交互时可能会用到。

4. STOS(Store String)串存入指令:stosb/stosw/stosd

功能:STOS指令用于将一个值存储到字符串中。根据操作数的大小,可以将AL(字节存储)、AX(字存储)或EAX(双字存储)中的值存储到ES:EDI指向的字符串元素中。

方向标志(DF)的影响

与其他字符串操作指令类似,方向标志DF影响EDI的更新方向。如果DF = 0(通过CLD指令设置),在每次存储操作后,EDI会按照操作数大小递增(对于字节存储加1,字存储加2,双字存储加4);如果DF = 1(通过STD指令设置),EDI会按照操作数大小递减。

例如,如果要反向填充一个双字串,可以先设置STD,然后使用STOSD指令进行操作。但要注意确保EDI的初始值是正确的,以便在反向存储操作时不会出现越界等错误。

字节存储(STOSB)

当使用STOSB指令时,将AL中的字节值存储到ES:EDI指向的字节串中的元素位置。例如,将一个特定的字节值(如0)填充到一个字节串缓冲区中。

.data
    buffer BYTE 10 DUP(?)
.code
    mov rdi, offset buffer
    mov al, 0
    cld ; 清除方向标志,使EDI递增(正向存储)
    mov rcx, 10
    rep stosb ; 重复存储字节,直到rcx为0

字存储(STOSW)

使用STOSW指令时,把AX中的字值存储到ES:EDI指向的字串元素中。这在处理16 - bit数据存储到字串的情况时使用。不过在64 - bit模式下,相对较少用于新编写的代码,但在与16 - bit代码交互或者处理遗留代码时可能会涉及。

双字存储(STOSD)

对于STOSD指令,是将EAX中的双字值存储到ES:EDI指向的双字串元素中。在需要将32 - bit数据存储到双字串的场景下使用,例如在处理特定格式的数据结构或者与32 - bit程序交互时。

64位汇编语言基础