【问题标题】:Loading value at address into register将地址处的值加载到寄存器中
【发布时间】:2018-01-29 06:56:42
【问题描述】:

作为学习练习,我一直在进行手写汇编。我似乎无法弄清楚如何将地址的值加载到寄存器中。

在语义上,我想做以下事情:

_start:
        # read(0, buffer, 1)
        mov     $3, %eax            # System call 3 is read
        mov     $0, %ebx            # File handle 0 is stdin
        mov     $buffer, %ecx       # Buffer to write to
        mov     $1, %edx            # Length of buffer
        int     $0x80               # Invoke system call

        lea     (%ecx, %ecx), %edi  # Pull the value at address into %edi
        cmp     $97, %edi           # Compare to 'a'
        je      done

我已经用 C 编写了一个更高级别的实现:

char buffer[1];
int main()
{
    read(0, buffer, 1);
    char a = buffer[0];
    return (a == 'a') ? 1 : 0;
}

但是使用gcc -S 编译产生的程序集不能很好地移植到我上面的实现中。

我认为lea 是我应该用来将存储在%ecx 中的给定地址处的值加载到%edi 中的正确指令,但是在gdb 中检查时,%edi 在之后包含一个垃圾值该指令被执行。这种方法正确吗?

【问题讨论】:

  • 不,lea 会给你有效的地址,即ecx + edi。看起来您想要mov,并且可能只读取一个字节并将其零扩展为双字的变体,因为您正在处理 ASCII 字符。

标签: linux assembly x86


【解决方案1】:

您需要的不是lea 指令,而是:

movzbl  (%ecx), %edi        

零扩展edi寄存器ecx中包含的内存地址处的字节。

_start:
        # read(0, buffer, 1)
        mov     $3, %eax            # System call 3 is read
        mov     $0, %ebx            # File handle 0 is stdin
        mov     $buffer, %ecx       # Buffer to write to
        mov     $1, %edx            # Length of buffer
        int     $0x80               # Invoke system call

        movzbl  (%ecx), %edi        # Pull the value at address ecx into edi
        cmp     $97, %edi           # Compare to 'a'
        je      done

一些建议

  • 您实际上并不需要movz 指令:您不需要单独的加载操作,因为您可以将ecx 指向的内存中的字节直接与cmp 进行比较:

    cmpb $97, (%ecx)
    
  • 您可能希望将要比较的字符(即'a')指定为$'a' 而不是$97,以提高可读性:

    cmpb $'a', (%ecx)
    
  • Avoiding conditional branches is usually a good idea。执行系统调用后,您可以立即使用以下代码使用cmov 来确定存储在eax 中的返回值,而不是执行条件跳转(即je 指令):

    xor     %eax, %eax     # set eax to zero
    cmpb    $'a', (%ecx)   # compare to 'a'
    cmovz   %edx, %eax     # conditionally move edx(=1) into eax
    ret                    # eax is either 0 or 1 at this point
    

    edx 在系统调用之前被设置为1。因此,上述这种方法依赖于 edx 在整个系统调用中保留的事实(即 int 0x80 指令)。

  • 更好的是,您可以在比较之后在al 上使用sete,而不是cmov

    xor     %eax, %eax     # set eax to zero
    cmpb    $'a', (%ecx)   # compare to 'a'
    sete    %al            # conditionally set al
    ret                    # eax is either 0 or 1 at this point
    

    如果ZF 标志由cmp 设置(即,如果@ 指向的字节,则通过xor %eax, %eax 设置为零的寄存器al 将设置为1 987654353@ 是'a')。使用这种方法,您无需考虑系统调用是否保留edx,因为结果不依赖于edx

【讨论】:

  • 您错过了将第二个示例更新为 movzx。在那个例子中,我认为你想要setz %al,因为你没有包含将%edx 设置为1 的指令。(而且你不想这样做)。无论如何,xor-zero eax / cmpb $'a', (%ecx) / setz %al 应该可以解决问题。 (如果您只返回一个 8 位 bool,您甚至可以省略异或归零,或者利用只有 main 的 int 返回值的低字节作为进程的退出状态可见的事实。
  • @PeterCordes 感谢您的评论。第二个代码 sn-p 出现在 执行系统调用(即int $0x80 指令)之后。寄存器edx 在系统调用之前 设置为1。我以为edx's value is preserved across the syscall。该代码还假定ecx 的值在系统调用中保留。我在这里错过了什么吗?
  • 哦,我想我没有意识到这仍然是在系统调用之后立即,在任何其他可能使用%edx 的代码之前。所以它不是一个独立的片段。是的,int $0x80 保留除了 EAX 中的返回值之外的所有寄存器。但仍然更喜欢setz 而不是cmov,在比较之前进行异或归零。当然,系统调用使这里的成本相形见绌,但在 Broadwell 之前,cmov 在英特尔上是 2 微秒。如果您需要生成 0/1,但无论使用哪个寄存器,EBX 和 EDX 上的cmov 都可以工作,因为您已经有了 0 和 1,并且在 AMD 和最近的 Intel 上会更好。
  • 顺便说一句,sete 在这种情况下具有正确的语义含义:如果最后一次比较是 equal,则设置。当然,它是setz 的同义词,但它有助于向人类读者表明将 ZF 设置在那里意味着什么。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-12-04
  • 1970-01-01
相关资源
最近更新 更多