assembly - 除以负数会导致 NASM 溢出

标签 assembly nasm x86-64

我正在自学一些使用 x86-64 Mac OS 的汇编编程。我试图找出为什么当将正整数除以负整数时会出现溢出。例如,5/-2 必须返回 -2。但是,就我而言,当我执行 -554/2 而不是 -277 时,它会返回 2147483371...这就是我所拥有的在我的汇编文件中:

; compiling using: nasm -f macho64 -o divide.o divide.s
[bits 64]
global _divide
section .text

; int divide(int dividend, int divisor)
_divide:

    xor rdx, rdx        ; making this to 0
    push rbp            ; base stack pointer
    mov rax, rdi        ; dividend
    mov rcx, rsi        ; divisor
    idiv rcx            ; integer division

    add rsp, 8
    ret

在我的 main.c 文件中,我有以下内容:

#include <stdio.h>
extern int divide(int dividend, int divisor);
int main(void)
{
    printf("divide: %d\n\n", divide(-554,2));
    return (0);
}

输出:除以:2147483371

有人可以向我解释一下我到底做错了什么吗?

最佳答案

32 位值 -554<sub>signed</sub> 相当于 4,294,966,742<sub>unsigned</sub> ,其中一半确实 2,147,483,371 ,即您得到的答案。所以它看起来像是一个签名/未签名的问题。并且,在检查 the x86 docs for idiv 后,我们看到:

IDIV r/m64 Signed divide RDX:RAX by r/m64, result stored in:
    RAX <- Quotient,
    RDX <- Remainder.

请注意第一行,特别是“有符号除rdx:rax”位。当 Intel 谈论 rdx:rax 时,它​​们指的是由这两个 64 位寄存器形成的 128 位值。假设这两个 64 位寄存器包含(十六进制)值:

rax : 01234567 89ABCDEF
rdx : 11112222 FFFFEEEE

那么 rdx:rax 值将是 128 位值:

rdx:rax : 11112222 FFFFEEEE 01234567 89ABCDEF

现在,因为您将 rdx 清零,所以组合值被认为,因为最高位为零。您实际上需要做的是将rax符号扩展rdx:rax中,这是一种在扩展值中保留符号的方法。例如,考虑 32 位 -1 ,正确和不正确地符号扩展为 64 位值:

         ffffffff     32-bit:                        -1.
ffffffff ffffffff     64-bit proper:                 -1.
00000000 ffffffff     64-bit improper:    4,294,967,295.

要正确地进行符号扩展,如果最右边的位(在您的情况下为 rdx)形成负数,则最左边的位(在您的情况下为 rax)应该全部为 1 位,否则全部为 0 位.

当然,那些聪明的英特尔工程师已经想到了这个用例,因此您可以使用 cqo convert-quadword-to-octoword 指令来实现,该指令的符号可以正确扩展。考虑到这一点,设置 eax 的代码将变为:

    mov   rax, rdi          ; Get dividend and
    cqo                     ;   sign extend to rdx:rax.
<小时/>

但是,您可能还会遇到额外问题。尽管 System V x86-64 ABI 指定参数在 64 位寄存器 ( rXX ) 中传递,但传递 32 位值实际上可能会留下包含垃圾的高位(我认为您可以在返回值的上部也留下垃圾。有关详细信息,请参阅 this excellent answer

因此,您不应该假设整个 64 位寄存器中只有最右边的 32 位中有一个正常的值。

在您的情况下(假设 32 位整数),您应该将符号扩展为 32 到 64,而不是 64 到 128,并使用较小宽度的除法指令。这会导致类似的结果:

global _divide
section .text

; int32_t divide(int32_t ediDividend, int32_t esiDivisor)
_divide:
    mov   eax, edi          ; Get 32-bit dividend and
    cdq                     ;   sign extend to 64-bit edx:eax.

    idiv  esi               ; Weave magic here,
                            ;   zeros leftmost rax.

    ret                     ; Return quotient in rax/eax.

这尚未经过测试,但应该可以满足您的要求。我实际上已经删除了 rbp 的推送,因为我很确定这是没有必要的。它似乎没有被损坏(这个函数既没有改变它,也没有调用任何其他可以改变它的函数),并且看起来你实际上从未在原始代码中正确恢复它。

关于assembly - 除以负数会导致 NASM 溢出,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51717317/

相关文章:

caching - Linux 是否将 x86 CPU 的 PCID 功能用于 TLB?如果不是,为什么?

performance - 为什么jnz不算循环?

汇编程序永远不会停止(Turbo 调试器)

assembly - 如何阅读汇编操作码引用?

c - 在 __m256i vector 中水平累积运行总计(前缀总和)

c++ - 如何使用 qmake 在 Windows 64 位上链接到 ACML?

linux - 尝试执行二进制文件时出现 "No such file or directory"错误

macos - Mac OS X 中的 NASM 错误

linux - 使用 stdin 读取文件后需要从 stdin 获取箭头键代码

linux - Nasm - 符号 `printf' 导致 R_X86_64_PC32 重定位溢出