除以负数会使我在NASM中产生溢出 [英] Dividing with a negative number gives me an overflow in NASM

查看:104
本文介绍了除以负数会使我在NASM中产生溢出的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在自学一些使用x86-64 Mac OS的汇编程序.我试图弄清楚为什么要将正整数与负整数相除会给我带来溢出.例如,5/-2必须返回-2.但是,就我而言,当我执行-554/2而不是-277时,它将返回2147483371.这就是我的汇编文件中的内容:

I'm teaching myself some assembly programming with x86-64 Mac OS. I'm trying to figure out why when it comes to dividing a positive integer with a negative integer gives me an overflow. For example, 5/-2 must return -2. However, in my case, it returns a 2147483371 when I do -554/2 instead of -277... This is what I have in my assembly file:

; 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文件中,我有这个:

In my main.c file, I have this:

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

输出:divide: 2147483371

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

Can someone explain to me what exactly I'm doing wrong?

推荐答案

32位值-554signed等效于4,294,966,742unsigned,而一半的值实际上是 ,答案是你得到了.因此,它看起来像一个已签名/未签名的问题.并且,在检查 idiv 的x86文档后,我们看到:

The 32-bit value -554signed is equivalent to 4,294,966,742unsigned and half of that is indeed 2,147,483,371, the answer you're getting. So it looks like a signed/unsigned issue. And, upon examining the x86 docs for idiv, we see:

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

请注意,第一行是有符号除以 rdx:rax 除"位.当英特尔谈论rdx:rax时,它们表示由这两个64位寄存器形成的128位值.假设这两个64位寄存器包含(十六进制)值:

Note that first line, specifically the "signed divide rdx:rax by" bit. When Intel talk about rdx:rax, they mean the 128-bit value formed from those two 64-bit registers. Assuming that those two 64-bit registers contain the (hex) values:

rax : 01234567 89ABCDEF
rdx : 11112222 FFFFEEEE

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

then the rdx:rax value will be the 128-bit value:

rdx:rax : 11112222 FFFFEEEE 01234567 89ABCDEF

现在,由于您要清零rdx,因此合并的值被视为,因为最高位为零.实际上, 要做的是将 sign-extend rax转换为rdx:rax,这是一种将符号保留在扩展值中的方法.例如,考虑32位-1,正确且不正确地将其符号扩展为64位值:

Now, because you are zeroing out rdx, the combined value is considered positive because the top bit is zero. What you actually need to do is sign-extend rax into rdx:rax, a method that preserves the sign in the extended value. For example, consider the 32-bit -1, sign-extended properly and improperly into a 64-bit value:

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

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

To sign-extend properly, the leftmost bits (rdx in your case) should be all one-bits if the rightmost bits (rax for you) form a negative number, all zero-bits otherwise.

当然,那些聪明的Intel工程师已经已经想到了这种用例,因此您可以使用cqo convert-quadword-to-octoword指令来做到这一点,该指令可以正确扩展.考虑到这一点,您用于设置eax的代码将变为:

Of course, those clever Intel engineers have already thought of just this use case, so you can do it with the cqo convert-quadword-to-octoword instruction, which sign extends correctly. With that in mind, your code for setting eax would become:

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


但是,您可能还会遇到 extra 问题.即使System V x86-64 ABI指定将参数传递到64位寄存器(rXX)中,传递32位值也可能实际上会留下包含垃圾的高位(我认为您是也允许在返回值的顶部留下垃圾.请参见


However, you may have an extra problem as well. Even though the System V x86-64 ABI specifies that the parameters are passed in the 64-bit registers (rXX), it's possible that passing 32-bit values will actually leave the upper bits containing rubbish (and I think you're allowed to leave rubbish in the upper parts of the return value as well. See this excellent answer for details.

因此,您应该假定整个64位寄存器中只有最右边的32位具有合理的值.

So you should not assume that you will have a sane value in the entire 64-bit register, only in the rightmost 32 bits.

在您的情况下(假定为32位整数),应使用较小宽度的除法指令对32到64而不是64到128进行符号扩展.这将导致更像:

In your case (assuming 32-bit integers), you should be sign extending 32-to-64 rather than 64-to-128, and using a smaller-width division instruction. That would result in something more like:

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的推动,因为我敢肯定这是没有必要的.它似乎没有被破坏(此函数既不会对其进行更改,也不会调用任何其他可以更改它的函数),并且看来您实际上从来没有在原始代码中正确地对其进行过恢复.

This is untested, but should do what you want. I've actually removed the pushing of rbp since I'm pretty certain it's not necessary. It appears not to be corrupted (this function neither changes it, nor calls any other function which could change it), and it appears you never actually restored it properly in your original code anyway.

这篇关于除以负数会使我在NASM中产生溢出的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆