GAS程序集片段除以0,不确定原因

时间:2013-03-13 09:36:33

标签: assembly x86 gas divide-by-zero i386

我有以下功能,涉及GAS语法中的i386程序集片段:

inline int MulDivRound(
    int nNumber,
    int nNumerator,
    int nDenominator )
{
    int nRet, nMod;

    __asm__ __volatile__ (
        "mov    %2,     %%eax   \n"
        "mull   %3              \n"
        "divl   %4              \n"
        "mov    %%eax,  %0      \n"
        "mov    %%edx,  %1      \n"

        :   "=m"    (nRet),
            "=m"    (nMod)
        :   "m"     (nNumber),
            "m"     (nNumerator),
            "m"     (nDenominator)
        :   "eax", "edx"
    );

    return nRet + nMod*2 / nDenominator;
}

我注意到,在少数情况下,我使用此功能遇到EXC_I386_DIV崩溃。以下调用产生了这样的崩溃:

int res = MulDivRound( 4096, -566, 400 );

我无法清楚地看到导致此函数除以0的情况:当然它只是将4096移动到eax,然后将其乘以-566,然后将其除以400,返回两个组件分工的结果。任何人都可以对此有所了解吗?

2 个答案:

答案 0 :(得分:5)

x86中的

除法/乘法指令...这段代码中出现了一些错误:

您已使用带有无符号 mul / div操作的已签名操作数。因此,真正执行的操作是:

  1. 已签名的-5660xfffffdca为2补全32位)被解释为无符号4294958538
  2. 这会乘以4096,从而导致175921837260800xfff:0xffdca000中的EDX:EAX)。请注意转换为-2318336 lower 32位,因为您预期"
  3. 完整的64位值除以400,但由于高位32位是0xfff4095),结果超出UINT32_MAX,例外情况是提高。
  4. 如果您通过在xor %%edx,%%edx之前插入divl来清除高位32位,则操作会成功,但它会返回您不期望的内容 - 即,它会分开0xffdca000 4292648960 400 0xa3c066导致10731622 EAX 0xa0 160,其余为EDX imul 1}})idiv

    那"正确"至于你指示机器做什么,但不是你所期望的。如果您想使用带符号的号码,则需要__asm__ __volatile__ ( "imull %3 \n" "idivl %4 \n" : "=a" (nRet), "=&d" (nMod) : "a" (nNumber), "mr" (nNumerator), "mr" (nDenominator) : "cc" ); / "m"

    最终可以将装配简化为以下内容:

    nMod

    这是因为gcc允许指定哪些寄存器用作输入/输出,所以这里根本不需要数据移动。此外,仅"=&d"(nMod)约束在64位上创建次优代码,因为它将参数强制到堆栈上;给它一个替代方案,生成的代码会更好。

    修改刚刚将(nDenominator)约束更改为EDX;它需要成为gcc所称的"早期的破坏者"。这意味着在消耗/使用所有输入操作数之前覆盖指定的输出寄存器,并告诉编译器不要在"m"中传递输入(特别是nNumerator)。否则,如果发生这种情况,它将导致一个有趣的"故障模式。如果您使用nDenominator / MulDivRound(INT32_MAX, 4, 2)的{​​{1}},这不是问题,但一旦允许寄存器,最好小心。

    Edit2:另请注意,上述代码当然不能防止溢出异常。您仍然可以像EDX一样调用它来触发它们。合法地/按照这些说明的设计方式。如果您必须确保不会发生这种情况,那么您必须添加代码,将RDX / [i]div与{{1}} / {{1}}之前的分母进行比较并处理它变小了。

答案 1 :(得分:4)

您没有得到除零错误,而是溢出错误

divl除以rdx:rax / operand(rdx中的高位字)并将结果存储在eax中,其余部分存储在edx中。

在您的代码中,您最终得到rdx=4095rax=0,因此您尝试将75539416981840613867520 / 400除以188848542454601534668 remainder 320

1888485424546015346680x 000a 3ccc cccc cccc cccc,不适合32位结果寄存器eax,因此出现溢出错误。

您需要确保rax包含您的值4095rdx=0。这在rax(result)和rdx(余数)中给出了正确的结果:

rax            0xa      10
rdx            0x5f     95