0

我有以下功能,涉及 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,返回除法运算结果的两个分量。任何人都可以对此有所了解吗?

4

2 回答 2

5

x86 中的除法/乘法指令......这段代码有一些问题:

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

  1. 有符号-5660xfffffdca作为 2 补码 32 位)被解释为无符号4294958538
  2. 乘以4096得到17592183726080( 0xfff:0xffdca000in EDX:EAX)。请注意转换为您“预期”的低32 位-2318336
  3. 完整的 64 位值除以400但由于高 32 位是0xfff, 4095),结果超出UINT32_MAX并引发异常。

如果您通过在xor %%edx,%%edx之前插入 an 来清除高位 32 位divl,则操作将成功,但它会返回您不期望的内容 - 即,它通过导致( ) in和余数( ) in来除0xffdca000( ) 。42926489604000xa3c06610731622EAX0xa0160EDX

就您指示机器执行的操作而言,这是“正确的”,但不是您期望的。如果要使用带符号的数字,则需要imul/idiv代替。

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

__asm__ __volatile__ (
    "imull   %3              \n"
    "idivl   %4              \n"
    :   "=a"    (nRet),
        "=&d"   (nMod)
    :   "a"     (nNumber),
        "mr"    (nNumerator),
        "mr"    (nDenominator)
    :   "cc"
);

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

编辑:刚刚将nMod约束更改为"=&d"(nMod); 它必须是 gcc 所说的“早期破坏者”。这意味着指定的输出寄存器在所有输入操作数被消耗/使用之前被覆盖,并告诉编译器不要(nDenominator)EDX. 否则,如果发生这种情况,将导致“有趣”的故障模式。如果您只使用"m"for nNumerator/这不是问题,nDenominator但是一旦允许使用寄存器,最好小心。

Edit2:还要注意,上面的代码当然不能证明溢出异常。你仍然可以称之为MulDivRound(INT32_MAX, 4, 2)触发那些。合法地/通过这些说明的设计方式。如果您必须确保不会发生这种情况,则必须添加代码,将分母与EDX/RDX之前的/ 进行比较,[i]div并处理分母较小的情况。

于 2013-03-13T11:54:45.667 回答
4

您不会得到除以零错误,而是溢出错误

divlrdx:rax / operand(rdx 中的高位字)并将结果存储eaxedx.

rdx=4095在您的代码中,您以and结尾rax=0,因此您尝试将75539416981840613867520 / 400结果划分为188848542454601534668 remainder 320.

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

你需要确保rax包含你的价值4095和那个rdx=0。这给出了 rax(结果)和 rdx(余数)的正确结果:

rax            0xa      10
rdx            0x5f     95
于 2013-03-13T10:03:05.907 回答