【问题标题】:Strcmp in nasm x86_64, registersnasm x86_64 中的 strcmp,寄存器
【发布时间】:2020-07-27 06:48:51
【问题描述】:

我正在尝试在 asm 中实现我自己的 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 = "你好世界" char *s2 = "Jdllo 世界" 结果是 1。 (0000 0001)

问题是当我尝试比较这些字符串时:

char *s1 = "Hello World"

char *s2 = "Hdllo 世界"

RAX 中的结果不是 1,而是 256。(0000 0001 0000 0000)

另一个例子:

char *s1 = "Hello World"

char *s2 = "Hcllo 世界"

RAX 中的结果不是 2,而是 512。(0000 0010 0000 0000)

正如您已经理解的那样,第三个不同字母的结果将是:

char *s1 = "Hello World"

char *s2 = "Heklo 世界"

RAX 中的结果不是 1,而是 65 536。(0000 0001 0000 0000 0000 0000)

我意识到 RAX 递增不正确,但我在代码中找不到错误。 所以我请你帮助我理解。

【问题讨论】:

  • 您正在使用 eax 从内存中加载和减去 32 位值。除了您看到的结果外,您还有可能超过 nul 终止字符。幸运的是,字符串长度加上 nul 终止符是 (4) 的倍数,并且尚未使用 'Hello Worle' 完成测试。使用 al - eax 的最低有效字节 - 解决了这个问题,但随后需要 符号扩展 al 到完整的 64 位 rax 返回值。
  • 我最后一句话错了。假设返回值为 32 位 'int',则只需对 32 位 eax 值进行符号扩展即可。

标签: assembly x86-64 nasm strcmp


【解决方案1】:

Assembly 与其他语言不同,所以很多时候您认为可能发生的事情并不是实际发生的事情。您得到令人惊讶/不正确结果的原因是因为您要减去两个数字。在获得调试器之前,您不会立即看到这一点(如果您想在汇编中学习/生存,您应该已经拥有它)。让我们看看在调试器的帮助下发生了什么。 首先让我们设置一个小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 以 10 为底数为 256。

由于该值非零,ZF(零标志)将不会被设置,您将跳转到.exit

希望您现在了解实际发生的情况。

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

【讨论】:

  • 亲爱的Waqar,非常感谢您如此深入的解释!我感谢您的帮助!现在我明白了,肯定会安装调试器来跟踪寄存器的变化
  • 另外,如果您陷入为某事创建逻辑的困境。转到godbolt.org,用 C/C++ 编写逻辑,查看汇编输出,然后在代码中执行相同的操作。并查看x86 Tag 以获取更多资源。
  • @mondrew:请注意,如果您确实想通过一次比较多个字节来运行得更快,您需要 bswap eax / bswap edx 所以每个输入的最高有效字节来自 最早的地址而不是最新的。 (然后进行 64 位减法,因此在结果中除了无符号 32 位数字之外还有符号位的空间,否则减法可能会导致有符号溢出...)但这很难用于 strcmp,因为您仍然需要检查每个字节都是终结者。 (相关:Why does glibc's strlen need to be so complicated to run quickly?
  • @mondrew:所以你在 x86-64 上实际做的是使用 SSE2 与 SIMD 并行执行 16 个单独的字节比较。 code.woboq.org/userspace/glibc/sysdeps/x86_64/strcmp.S.html 读起来有点乱,#ifdef 可以作为 strcmp 或 strcasecmp 工作,不同大小的展开循环可以完全提升大输入。
猜你喜欢
  • 2018-07-13
  • 1970-01-01
  • 2015-03-28
  • 1970-01-01
  • 2015-12-26
  • 2014-02-17
相关资源
最近更新 更多