程序集 x86:比较字符串不起作用

Assembly x86: comparing strings doesn't work

我正在尝试在 Assembly 中编写一个程序来检查两个字符串。

section .data
str1 db 'mystring'
str2 db 'mystring'

output db 'cmp went fine'
len equ $-output

section .text
global main

main:
    mov ecx, str1
    cmp ecx, str2
    je ifBody0
    int 80h

    mov eax, 1
    mov ebx, 0
    int 80h

ifBody0:
    mov eax, 4
    mov ebx, 1
    mov ecx, output
    mov edx, outputlen
    int 80h

奇怪的是,当我调用条件跳转:je [label]时,它不起作用。但是当我将 je 更改为 jne 时,它起作用了。 我想知道我在这里做错了什么。

让我们从这两个开始:

str1    db 'mystring'
        mov ecx,str1

用汇编程序编译后,机器代码的原始字节看起来像这样(这将在加载可执行文件后成为内存内容):

6D 79 73 74 72 69 6E 67  mystring
B9 00 00 00 00           ¹....

最后 4 个零是来自 'mystring' 的 'm' 字节的地址,因为我决定它将在地址 0 处编译。前 8 个字节是字符串数据(ASCII 编码),B9mov ecx,imm32 指令操作码。

你不能将字符串放入ecxecx是32位宽(4字节),而字符串可以有很多字节。因此,使用 ecx 你最多可以从字符串中获取 4 个字节,但这需要 mov ecx,DWORD [str1],这会将值 0x7473796D 放入 ecx(x86 是小端,所以第一个字节 6D 在 DWORD (32b) 值中是最低位的。

但是mov ecx,str1str1符号加载ecx,这是第一个'm'字节(0x00000000)的地址。

要比较两个字符串,您需要将两个地址加载到一些寄存器中,然后从这些地址加载字节,并逐个比较它们,直到找到一些差异(或字符串结尾)(有更快的算法,但它们更复杂,需要您知道前面字符串的长度,而逐字节比较可以轻松地处理类似 C 的零终止字符串。

谈到字符串的长度,你应该以某种方式定义一个。在 C 语言中,通常在字符串的最后一个字符之后放置零(在该示例中将位于 B9 之前),在 C++ 中,std::string 是将长度作为直接值 fetch/compare 的结构。或者您可以在源代码中对其进行硬编码,例如 outputlen.

当您在汇编程序中编程时,您应该始终知道您正在处理多少位,并选择正确的寄存器大小(或扩展值)和正确的内存缓冲区大小,以处理所需的值。

对于字符串,这意味着您必须决定字符串的编码。 ASCII 是每个字符 8 位(1 个字节),UTF-8 每个字形的字节数可变,早期版本的 UTF-16 (UCS-2) 每个字形有 2 个字节(如 Java,但当前的 Utf- 16 是可变长度),Utf-32 每个字形固定 4 个字节。因此,使用 ASCII 编码的字符串来获取它的第一个字符意味着执行 mov al,BYTE [str1](或 mov ecx,str1 mov al,[ecx] -> al = 6Dh = 'm')使用 Utf-32 获取第二个字符,您必须做 mov eax,DWORD [utf32str + 4]。使用 Utf-8,单个字符最多可以有 1 到 6 个字节的 IIRC,因此您必须以相当复杂的方式处理它,以识别有效的 utf-8 代码并读取正确的字节数。但是如果你只是想知道两个 utf-8 字符串是否位相等,你可以逐字节比较它们,而不需要处理字形本身。

当然,您应该了解寄存器的大小以及在 x86 上如何寻址某些寄存器的子部分的方式,即。比如 ax 部分(低 16b)从整个 eax(32b)中分离出来,或者 ah:al(高 8b : 低 8b)如何组合在一起 ax .


我希望你在这之后会明白,你确实比较了两个指针(str1 vs str2),它们总是不相等的,因为它们指向内存中的不同字节。而不是比较内存中的内容(字符串)。

为了比较 x86-assembly 中的字符串,有一个名为 CMPS(Compare Strings) 的特殊操作码。对于 BYTE 字符串,相关的 OpCode 是 CMPSB。您可以通过将 ESI 设置为源字符串并将 EDI 设置为目标字符串来使用它。等式检查的长度(最好是最长的字符串)设置在ECX中。 小心溢出!

因此您的代码可能如下所示:

section .data
str1 db 'mystring',0
str1len equ $-str1
str2 db 'mystring',0

output db 'cmp went fine',0x0a,0
outputlen equ $-output
output2 db 'cmp went wrong',0x0a,0
output2len equ $-output2

section .text
global main

main:
    lea esi, [str1]
    lea edi, [str2]
    mov ecx, str1len  ; selects the length of the first string as maximum for comparison
    rep cmpsb         ; comparison of ECX number of bytes
    mov eax, 4        ; does not modify flags 
    mov ebx, 1        ; does not modify flags 
    jne ifWrong       ; checks ZERO flag

ifRight:              ; the two strings do match
    mov ecx, output
    mov edx, outputlen
    int 80h
    jmp exit
ifWrong:              ; the two strings don't match
    mov ecx, output2
    mov edx, output2len
    int 80h
exit:                 ; sane shutdown
    mov eax, 1
    mov ebx, 0
    int 80h