程序集 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 编码),B9
是 mov ecx,imm32
指令操作码。
你不能将字符串放入ecx
,ecx
是32位宽(4字节),而字符串可以有很多字节。因此,使用 ecx
你最多可以从字符串中获取 4 个字节,但这需要 mov ecx,DWORD [str1]
,这会将值 0x7473796D
放入 ecx
(x86 是小端,所以第一个字节 6D
在 DWORD (32b) 值中是最低位的。
但是mov ecx,str1
用str1
符号加载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
我正在尝试在 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 编码),B9
是 mov ecx,imm32
指令操作码。
你不能将字符串放入ecx
,ecx
是32位宽(4字节),而字符串可以有很多字节。因此,使用 ecx
你最多可以从字符串中获取 4 个字节,但这需要 mov ecx,DWORD [str1]
,这会将值 0x7473796D
放入 ecx
(x86 是小端,所以第一个字节 6D
在 DWORD (32b) 值中是最低位的。
但是mov ecx,str1
用str1
符号加载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