在NASM x86_64中的Strcmp,寄存器

3

我正试图在汇编语言中实现自己的strcmp函数。以下是ft_strcmp.s文件内容:

global ft_strcmp

section .text
ft_strcmp:
            mov eax, [rdi]
            sub eax, [rsi]
            jne .exit
            cmp byte [rdi], 0 ; if s1 end
            je .exit
            cmp byte [rsi], 0 ; if s2 end
            je .exit
            inc rdi
            inc rsi
            jmp ft_strcmp
.exit:
            ret

第一个字母的比较没有问题: char *s1 = "Hello World" char *s2 = "Jdllo World" 结果为1。(0000 0001)
问题在于当我尝试比较这些字符串时:
char *s1 = "Hello World"
char *s2 = "Hdllo World"
RAX中的结果不是1,而是256。(0000 0001 0000 0000)
另一个例子:
char *s1 = "Hello World"
char *s2 = "Hcllo World"
RAX中的结果不是2,而是512。(0000 0010 0000 0000)
正如你已经理解的那样,第三个不同字母的结果将是:
char *s1 = "Hello World"
char *s2 = "Heklo World"
RAX中的结果不是1,而是65536。(0000 0001 0000 0000 0000 0000)
我意识到RAX的递增不正确,但我找不到代码中的错误。 所以我请求您帮助我理解。

3
您正在使用 eax 从内存中加载并减去32位的值。除了您看到的结果之外,您还有可能超过空字符终止符。只是因为字符串长度加上空字符终止符都是 (4) 的倍数,而且测试没有使用 'Hello Worle' 这样的字符串。使用 al - 即 eax 的最低有效字节 - 可以解决这个问题,但接下来需要将 al 进行符号扩展,以获得完整的64位 rax 返回值。 - Brett Hale
2
我的最后一句话是错误的。假设32位 'int' 返回值,您只需要将符号扩展到32位 eax 值即可。 - Brett Hale
1个回答

4

汇编语言与其他语言不同,所以往往会发生你认为的事情实际上并没有发生。你得到意外/错误的结果是因为你正在进行两个数字的减法运算。除非使用调试器(如果你想学习/在汇编中生存,应该已经有了),否则你将无法立即看到这一点。让我们借助调试器来看看正在发生什么。 首先,让我们设置一个小的main和一些数据:

section .data
    str1: db "Hello world",0
    str2: db "Hdllo world",0

section .text
global main

ft_strcmp:
... ; your code here

main:
   nop
   mov rdi, str1
   mov rsi, str2
   
   call ft_strcmp
   nop

当执行开始时,我们使用字符串(即一个字节序列)加载 rdirsi。重要的是,rdirsi 实际上并未“包含” 字符串/字节,而是指向它们的地址,也就是说,rdirsi 包含的是我们字符串所在位置的地址。
接下来,我们调用函数,这是问题开始出现的地方。我将重点关注以下两条指令:
    mov eax, [rdi] ;1
    sub eax, [rsi] ;2

1指令中,你将字符串本身移动到了eax寄存器中。 [rdi]的意思是获取rdi地址处的值,就像解引用指针一样。现在,eax的大小为32位(4字节),因此它只能包含4个字节。并且假设你有一个小端系统,字节的顺序将是反向的,因此eax中的值为:

eax = 0x6c6c6548

如果你仔细看,你会发现它距离str1只有4个字节:

6c  6c   65  48
'l' 'l' 'e'  'h'

接下来,您需要从rsi地址中的值减去此数字,该地址中的值为:
0x6c6c6448
OR
0x6c 6c  64  48
'l' 'l' 'd'  'h'

如果你减去这两个数字:

0x6c6c6548 - 0x6c6c6448 = 0x100

0x100在十进制中是256。

由于该值为非零值,因此ZF(零标志位)将不会被设置,你将跳转到.exit

希望你现在明白了实际发生的情况。

我强烈建议获取一个调试器并使用它来调试此类问题。


1
亲爱的Waqar,非常感谢您如此深入的解释!我很感激您的帮助!现在我明白了重点,并且一定会安装调试器来跟踪寄存器的变化。 - mondrew
此外,如果您在创建某些逻辑时遇到困难,请前往godbolt.org,用C/C++编写逻辑,查看汇编输出,然后在您的代码中执行相同的操作。并查看x86标签以获取更多资源。 - Waqar
@mondrew:请注意,如果您确实想通过同时比较多个字节来加快运行速度,则需要执行 bswap eax / bswap edx,以便每个输入的最高有效字节来自最早的地址而不是最新的地址。(然后进行64位减法运算,这样在结果中除了无符号32位数字之外还有符号位的空间,否则减法可能会发生带符号溢出...)但是,这对于 strcmp 很难使用,因为您仍然必须检查每个字节是否为终止符。(相关:为什么 glibc 的 strlen 需要如此复杂才能快速运行? - Peter Cordes
1
@mondrew:在x86-64上实际上所做的是使用SSE2并行进行16个单独的字节比较。 https://code.woboq.org/userspace/glibc/sysdeps/x86_64/strcmp.S.html 阅读起来有点混乱,因为有#ifdef用于作为strcmp或strcasecmp,并且不同大小的展开循环以完全升级大型输入。 - Peter Cordes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接