【问题标题】:Comparing character with Intel x86_64 assembly将字符与 Intel x86_64 程序集进行比较
【发布时间】:2016-06-30 03:21:05
【问题描述】:

我是汇编新手(Intel x86_64),我正在尝试重新编码 C 库中的一些函数。我在 64 位 Linux 上使用 NASM 进行编译。

我的 strchr 函数出现错误,我找不到解决方案...

提醒一下,这里是 strchr 手册页的摘录:

char *strchr(const char *s, int c);

strchr() 函数返回一个指针,指向字符串 s 中字符 c 的第一次出现。

这是我尝试过的:

strchr:
    push rpb
    mov rbp, rsp

    mov r12, rdi ; get first argument
    mov r13, rsi ; get second argument

    call strchr_loop


strchr_loop:
    cmp [r12], r13 ; **DON'T WORK !** check if current character is equal to character given in parameter... 
    je strchr_end  ; go to end

    cmp [r12], 0h  ; test end of string
    je strchr_end  ; go to end

    inc r12        ; move to next character of the string

    jmp strchr_loop ; loop


strchr_end
    mov rax, r12    ; set function return

    mov rsp, rbp
    pop rbp

这会在字符串的 ned 上返回一个指针,但找不到字符...

我认为是这条线不起作用:

    cmp [r12], r13

我对此进行了测试,并且成功了:

    cmp [r12], 97 ; 97 = 'a' in ASCII

例子:

char *s;

s = strchr("blah", 'a');
printf("%s\n", s);

返回:

ah

但是我不能让它与寄存器比较一起工作。我做错了什么,我该如何解决?

【问题讨论】:

  • r13 是一个 64 位寄存器。你的字符是一个字节。也许您打算使用r13b
  • 你需要保存和恢复你破坏的被调用者保存的寄存器。我知道的所有调用 x86-64 调用约定都要求保留 r12r13
  • 获取有关 Linux 上 64 位代码调用约定的信息的最佳位置是查看 System V 64 位 ABI。特别是如果您想要调用者/被调用者寄存器列表及其用法,则可以在第 21 页的Figure 3.4 Register Usage 中找到它们。
  • 对于不需要保存/恢复的暂存寄存器,首选使用 eax/ecx/edx,因为使用它们的指令不需要 REX 前缀。除了代码大小之外没有速度差异,但代码大小很重要。你可以增加rsi;无需将其复制到另一个寄存器。请参阅x86 tag wiki,尤其是 Agner Fog 的指南。
  • 调试器中的单步执行是必不可少的,顺便说一句。您是否尝试过,而不仅仅是查看返回值?

标签: linux assembly nasm x86-64


【解决方案1】:

首先,感谢您的帮助!我想我对自己在做什么有了更好的了解。

我遇到了接收8bits参数而不是64bits的问题rdi...但是朋友告诉我第一个8bit参数也在sil 注册。

这是我的工作代码:

strchr:

   push rpb
   mov rbp, rsp

   call strchr_loop


strchr_loop:
    cmp byte [rdi], sil ; check if current character is equal to character given in parameter
    je strchr_end  ; go to end

    cmp byte [rdi], 0h  ; test end of string
    je strchr_end  ; go to end

    inc rdi        ; move to next character of the string

    jmp strchr_loop ; loop


 strchr_end
    mov rax, rdi    ; set function return

    mov rsp, rbp
    pop rbp

请随时告诉我是否有改进的方法,再次感谢!

【讨论】:

  • 你有一个流浪的call指令。您的代码仍然有效的唯一原因是您正在从rbp 恢复rsp,因此call strchr_loop 推送的返回地址被丢弃。对于不匹配的调用/返回,这会给您带来很大的性能损失。只需删除该行,让执行自然地落入循环的顶部。
  • 就像我在这个问题上评论的那样,更好的代码是:mov al, [rdi] / test al,al/jz / cmp al,sil/je 或者movzx eax, byte [rdi] / test eax,eax/jz / cmp eax,esi/je。不过,您的方法还不错,因为您没有对内存进行任何读-修改-写操作。您也不需要使用rbp 制作堆栈帧;这是可选的。
  • @PeterCordes 感谢您的建议!我终于删除了`call strchr_loop`。这对我来说就像调用一个函数,但我知道在这种情况下它是没有用的。 “用 rbp 制作堆栈帧”是什么意思?我在代码开头和结尾所做的 push et pop ?我不太了解这些行,它们来自我组装课的唯一示例^^'
  • 通过“制作堆栈框架”,是的,我的意思是 rbp。见eli.thegreenplace.net/2011/09/06/stack-frame-layout-on-x86-64。我略略浏览了一下。 -fomit-frame-pointer 已经成为 gcc 的默认设置有一段时间了,因为可执行文件中的元数据使得回溯成为可能,而不是遵循通过推送 rbp 创建的堆栈帧的链表。
  • @PeterCordes 哦,好吧,我想我明白了!我删除了这些行,它仍在工作,所以这意味着少了 4 行。再次感谢!
【解决方案2】:

这里是您的汇编代码的修复,它在手册页中定义的 x86-64 汇编中实现了 strchr(3):

asm_strchr:
        push rbp
        mov rbp, rsp

strchr_loop:
        cmp byte [rdi], 0  ; test end of string
        je fail_end     ; go to end

        cmp byte [rdi], sil ; check if current character is equal to character given in parameter
        je strchr_end   ; go to end

        inc rdi         ; move to next character of the string
        jmp strchr_loop         ; loop

strchr_end:
        mov rax, rdi            ; set function return
        mov rsp, rbp
        pop rbp
        ret

fail_end:
        mov rax, 0
        mov rsp, rbp
        pop rbp
        ret

【讨论】:

  • 如果代码不使用序言(1x)和尾声(2x),为什么还要包含它?您可以使用xor eax, eax 有效地将RAX 归零
  • 您没有修复 OP 代码中的任何其他低效问题(例如 loop structure),或者两次访问内存。复制尾部会使与 RBP 混淆的代码大小成本变得更糟。当然,没有办法编写一次只检查 1 个字节的高效strchr;在 x86-64 上,几乎总是值得使用 SSE2 pcmpeqb,除非您希望在前几个字节内找到字符,因此优化一个字节一次 strchr 主要只是一个练习。
猜你喜欢
  • 1970-01-01
  • 2016-06-25
  • 1970-01-01
  • 2021-08-27
  • 1970-01-01
  • 1970-01-01
  • 2012-10-06
  • 2019-01-22
  • 1970-01-01
相关资源
最近更新 更多