2013-03-13 40 views
0

我有以下的功能,涉及的i386装配的气体语法的一个片段:气体装置片段由0分,不知道为什么

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

+0

单步执行调试器中的代码,查看每条指令之前的寄存器值。 – Michael 2013-03-13 09:57:10

回答

5

师/乘法指令......有几件事错在下面的代码:

你使用签署操作数与无符号mul/div操作。你真的因此执行的操作是:

  1. 签署的-5660xfffffdca为2补32位)被解释为无符号4294958538
  2. 这是由4096导致175921837260800xfff:0xffdca000EDX:EAX)相乘。注意 32位的是转换为-2318336为你“期望”
  3. 完整的64位值由400但由于分成的事实,上32位是0xfff4095),结果超出UINT32_MAX和例外上调。

如果清除通过插入divl之前xor %%edx,%%edx上32位,操作会成功,但它会回报你的东西,你不要指望 - 由400即,它分为0xffdca0004292648960)导致0xa3c06610731622EAX和其余的0xa0160EDX

就您指示机器执行的操作而言,这是“正确的”,但不是您所期望的。如果您想使用带符号的号码,则需要使用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);它需要成为海湾合作委员会称之为“早期破坏者”的东西。这意味着指定的输出寄存器在所有输入操作数被消耗/使用之前被覆盖,并且告知编译器不要通过EDX中的输入(特别是(nDenominator))。否则,如果发生这种情况,会导致“有趣”的失败模式。这是不是一个问题,如果你只有使用"m" 0123'/nDenominator但一旦允许寄存器,最好小心。

编辑2:另请注意,上述代码当然不能防止溢出异常。你仍然可以将它称为MulDivRound(INT32_MAX, 4, 2)来触发它们。合法地/按照这些说明书的设计方式。如果您必须确保不会发生这种情况,则必须添加代码,以便在[i]div之前将分母与EDX/RDX进行比较,并处理较小的情况。

+0

+1“没有必要的数据移动”的好解决方案 - 只是想用一些代码更新我的答案,但是这会包含从edx到eax的MOV :) – 2013-03-13 12:01:42

+0

@安德烈亚斯:谢谢。使用'“= a”'resp。在x86上的''''''''''''''''''''''''''''''div'操作码是''= d“'(和/或输入相同)是众所周知的。另一个例子是http://stackoverflow.com/a/10781271/512360。有一点需要特别注意''我'div'就是你提到的 - 在大多数情况下,你需要清除'EDX' /'RDX'或签名扩展'EAX' /'RAX' 'CDQ' /'CQTO'指令),然后再调用'[i] div'。因为'imul'已经做了正确的事情,所以在这里并不需要这个具体案例。 – 2013-03-13 13:47:01

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(结果)和RDX(余数)的正确的结果:在86

rax   0xa  10 
rdx   0x5f  95 
+0

感谢您的回答!目前就在分割指令之前,我有'rax = 0x00000000ffdca000'和'rdx = 0x0000000000000fff'。我将使用什么指令将这些值存入分隔指令的正确寄存器中? – benwad 2013-03-13 11:47:36

+0

查看@ FrankH。的回答问题的答案 – 2013-03-13 12:04:04

相关问题