ben*_*wad 0 x86 assembly gnu-assembler 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,返回除法运算结果的两个分量.任何人都可以对此有所了解吗?
x86中的除法/乘法指令...这段代码中有一些错误:
您正在使用带有unsigned mul/divoperations的带符号操作数.因此,您真正执行的操作是:
-566(0xfffffdca作为2补码32位)被解释为无符号42949585384096得到17592183726080(0xfff:0xffdca000in EDX:EAX).注意转换为你期望的低 32位-2318336400但由于高32位是0xfff,4095),结果超出UINT32_MAX并引发异常.如果你通过在xor %%edx,%%edx前面插入一个清除高位32位divl,操作将会成功,但它会返回你不期望的东西 - 也就是说,它通过导入in ()和其余的()in 来分割0xffdca000(4292648960).4000xa3c06610731622EAX0xa0160EDX
就你指示机器做什么而言,这是"正确的",但不是你所期望的.如果您想使用带符号的数字,则需要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并处理它较小的情况.
| 归档时间: | 
 | 
| 查看次数: | 318 次 | 
| 最近记录: |