Warning: file_get_contents(/data/phpspider/zhask/data//catemap/0/assembly/6.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Assembly 用负数除法会在NASM中产生溢出_Assembly_Nasm_X86 64 - Fatal编程技术网

Assembly 用负数除法会在NASM中产生溢出

Assembly 用负数除法会在NASM中产生溢出,assembly,nasm,x86-64,Assembly,Nasm,X86 64,我在自学x86-64 Mac操作系统的汇编编程。我想弄明白为什么当一个正整数除以一个负整数时会产生溢出。例如,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, i

我在自学x86-64 Mac操作系统的汇编编程。我想弄明白为什么当一个正整数除以一个负整数时会产生溢出。例如,
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);
}
#包括
外部整数除法(整数除数,整数除数);
内部主(空)
{
printf(“divide:%d\n\n”,divide(-554,2));
返回(0);
}
输出
除:2147483371


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

32位值
-554signed
相当于
4294966742 unsigned
,其中一半确实是
2147483371
,您得到的答案。因此,它看起来像一个已签名/未签名的问题。通过研究,我们发现:

然后,
rdx:rax
值将是128位的值:

rdx:rax : 11112222 FFFFEEEE 01234567 89ABCDEF
         ffffffff     32-bit:                        -1.
ffffffff ffffffff     64-bit proper:                 -1.
00000000 ffffffff     64-bit improper:    4,294,967,295.
现在,由于您正在将rdx归零,因此组合值被视为正值,因为顶部位为零。实际上需要做的是将
rax
符号扩展到
rdx:rax
,这是一种将符号保留在扩展值中的方法。例如,考虑32位<代码> -1 < /代码>,将符号适当地扩展和不正确地扩展成64位值:

rdx:rax : 11112222 FFFFEEEE 01234567 89ABCDEF
         ffffffff     32-bit:                        -1.
ffffffff ffffffff     64-bit proper:                 -1.
00000000 ffffffff     64-bit improper:    4,294,967,295.
为了正确地进行符号扩展,如果最右边的位(
rax
)形成负数,则最左边的位(
rdx
)应全部为一位,否则全部为零位

当然,那些聪明的英特尔工程师已经想到了这个用例,所以您可以使用
cqo
将四字转换为八字
指令来实现,该指令的符号扩展正确。考虑到这一点,设置
eax
的代码将变成:

    mov   rax, rdi          ; Get dividend and
    cqo                     ;   sign extend to rdx:rax.

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

因此,您不应该假设在整个64位寄存器中只有最右边的32位具有sane值

在您的情况下(假设为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
,因为我非常确定这不是必需的。它似乎没有损坏(此函数既不更改它,也不调用任何其他可能更改它的函数),而且似乎您从未在原始代码中正确还原过它。

您的代码也因负除数而被破坏:
除(5,-2)
将得到零。这纯粹是通过调用约定来解释的。您的零扩展名而不是符号扩展名错误(请参阅@paxdiablo的答案)只有负红利才重要


您告诉编译器您的函数采用
int
args,并且
int
是x86-64 System V调用约定中的32位类型

您假设您的输入是符号扩展到64位的,但调用约定并不要求这样,因此编译器不会在10字节
mov r64、imm64上浪费代码大小,因为它可以使用5字节
mov r32、imm32

有关更多详细信息,请参见以下问答(第二个基本上是第一个问答的副本):


因此,编译器将为
main
发出如下代码:

mov    edi, 5      ; RDI = 0x0000000000000002
mov    esi, -2     ; RSI = 0x00000000FFFFFFFE
call   _divide
我检查过了,这就是gcc和clang真正做到的,即使对于未优化的代码也是如此


对于
除以(5,-2)
,您的代码将导致

  • RDX=0,RAX=5。即股息=0x0000000000000000:00000000000000005,这是正确的。(零和符号扩展对于非负输入是相同的操作)
  • 除数=0x00000000FFFFFE=+4294967294,大且为正
64位
idiv
计算
5/4294967294
产生商=RAX=0,余数=RDX=5

如果您只修复了类型宽度/操作数大小不匹配的错误,您仍然会遇到诸如@paxdiablo的答案所解释的负红利问题。但是这两个修复对于
除法(-554,2)
实际工作是必要的。


那么你应该怎么写呢? 您可以将原型更改为
int64\t
long
(在x86-64系统V中为64位),并使用
cqo
设置有符号除法。()

或者您可以使用
movsxd-rax、edi
/
movsxd-rcx、esi
将32位输入扩展到64位。但这太傻了。只需使用32位操作数大小,因为这是您告诉编译器要传递的

这很好,因为64位除法比32位除法慢得多

这就是我要做的:

global _divide
; inputs: int32_t dividend in EDI, int32_t divisor in ESI
; output: int32_t quotient in EAX,  int32_t remainder in EDX
;  (C callers won't be able to access the remainder, unfortunately)
_divide:
    mov     eax, edi
    cdq                    ; sign-extend the dividend into edx:eax

    idiv    esi            ; no need to copy to ecx/rcx first
    ret
无需推送RBP;我们不调用任何其他函数,因此重新调整堆栈并不重要,我们也不修改RBP以用作帧指针

我们可以在不保存/恢复RDX的情况下关闭它:它是x86-64 System V和Windows x64中的一个调用关闭寄存器(与大多数32位调用约定相同)。这是有意义的,因为一些常见指令(如
idiv
)会隐式使用它

如果您用C编写gcc和clang(当然启用了优化),它就会发出这样的信息

int divide(int dividend, int divisor) {
    return dividend / divisor;
}
(请参阅上面的Godbolt链接,其中我将其与
\uuuu属性(noinlin)一起包含。)
    mov     edi, 5
    mov     rsi, -2
    call    _divide