如何在汇编语言X86中检测溢出条件

时间:2012-03-21 01:32:18

标签: c assembly x86

我有一项任务,我们必须编写两个函数。还必须使用处理器的条件代码检测溢出条件,并返回0以指示已遇到错误。我能够写出这些功能。

 .file  "formula.c"  
    .text
.globl _nCr  
    .def    _nCr;   .scl    2;  .type   32; .endef  
_nCr:  
        pushl   %ebp  
    movl    %esp, %ebp  
    subl    $56, %esp  
    movl    8(%ebp), %eax  
    movl    %eax, (%esp)  
    testl %eax, %eax  
    call    _factorial  
    movl    %eax, -12(%ebp)  
    movl    12(%ebp), %eax  
    addl    $1, %eax  
    movl    %eax, (%esp)  
    call    _factorial  
    movl    %eax, -16(%ebp)  
    movl    12(%ebp), %eax  
    notl    %eax  
    addl    8(%ebp), %eax  
    movl    %eax, (%esp)  
    call    _factorial  
    movl    %eax, -20(%ebp)  
    movl    -16(%ebp), %eax  
    movl    %eax, %edx  
    imull   -20(%ebp), %edx  
    movl    %edx, -28(%ebp)  
    movl    -12(%ebp), %eax  
    movl    %eax, %edx  
    sarl    $31, %edx  
    idivl   -28(%ebp)  
    leave  
    ret  
.globl _factorial   
    .def    _factorial;  .scl    2;     .type   32;     .endef   
_factorial:  
    pushl   %ebp  
    movl    %esp, %ebp  
    subl    $16, %esp  
    movl    $1, -8(%ebp)  
    movl    $1, -4(%ebp)  
    jmp L3  
L4:   
    movl    -8(%ebp), %eax   
    imull   -4(%ebp), %eax  
    movl    %eax, -8(%ebp)  
    addl    $1, -4(%ebp)   
L3:
    movl    -4(%ebp), %eax  
    cmpl    8(%ebp), %eax  
    jle L4  
    movl    -8(%ebp), %eax  
    leave  
    ret  
    .def    ___main;    .scl    2;  .type   32; .endef  
    .section .rdata,"dr"  
    .align 4  

此功能基本上是n!/r!(n-r)!。当数字变大时,溢出发生在阶乘。我只是不明白我将如何设置溢出条件。

3 个答案:

答案 0 :(得分:2)

1)您的算术命令是可能设置溢出位的操作

2)" JO" (跳过溢出)和" JNO" (跳过不溢出)允许你分支,这取决于是否发生溢出

3)你可能只是设置"%eax" " JO"。

之后的0

4)如果您还不熟悉它,那就是优秀,优秀的资源:

Programming from the Ground Up, Jonathan Bartlett

答案 1 :(得分:1)

在x86架构上,当执行诸如addl 8(%ebp), %eax之类的算术指令时,在CPU状态字中设置条件代码。有些指令的行为取决于条件代码。

您可以让代码在给定条件下采用备用路径(执行分支)。 x86在Jxx助记符JA, JAE, JB, JBE, JC, JCXZ, ..., JZ下有一系列条件分支指令。例如JZ表示如果为零则跳转:如果指令产生零结果,则设置分支,设置零标志。 JO在溢出时跳跃。

条件也可以转换为字节数据并存储到寄存器或存储器中。这对于编译C表达式很有用,例如:

 x = (y != 3); /* if (y != 3) x = 1; else x = 0 */

它由SETx指令组完成,这些指令也很多,如条件分支:SETA, SETAE, SETB, ..., SETZ。例如,如果零条件为真,SETZ将给定字节设置为1。 E.g。

 seto %bl  /* set bottom byte of B register to 1 if overflow flag set */

答案 2 :(得分:0)

大多数指令在有符号溢出时设置OF,而在无符号溢出时设置CF。 http://teaching.idallen.com/dat2343/10f/notes/040_overflow.txt解释添加/订阅。 (按位布尔指令(例如和/或/ xor不会溢出,因此它们总是清除CF和OF)。

imul在整个结果不是下半部分结果的符号扩展(与输入宽度相同)时同时设置OF和CF。这甚至适用于高效的2操作数形式,该形式不会在任何地方写高半部分。他们仍然根据情况设置标志。如果对乘法进行无符号溢出检测,则需要使用笨拙的单操作数mul

当商不适合AL / AX / EAX / RAX(取决于操作数大小)时,除会引发#DE异常。不幸的是,没有办法抑制/屏蔽它,因此除非有信号处理程序来捕获SIGFPE(在POSIX OS上),否则您不能尝试使用大分红的2N / N => N位除法并在事后检测到溢出。 。或者在裸机上,使用#DE的中断处理程序。

专门用于组合:

您不必天真地计算n!,而是可以更早取消并只计算prod(r+1 .. n)。实际使用较大的rn-r,然后除以另一个。

最后还是要除以一个可能的大数,这样就不能消除所有适合32位整数的可能结果的溢出机会。但是它扩展了您可以轻松处理的范围,并且当然会更快,因为您要做的乘法次数更少。例如C(999, 1000)只做1000 / (1000-999)!,所以没有乘法,只有一个div

如果使用mul指令对乘积进行最后的乘法以在EDX:EAX中产生64位结果,则可以将其用作32位除法的64位除数。 (如果您想冒例外的危险。)

mul是NxN => 2N乘法,因此,如果仅在循环中使用它,它将忽略先前输出的上半部分。如果您按从低到高的顺序进行乘法运算,那么最后的乘法运算就是该范围的高端,这将为您提供最大的范围。

例如,您可能会这样做

// mix of C pseudocode and AT&T 32-bit.
//  Some real registers, some var names: pick registers for those.

   if (n == r) return 1;
   divisor = factorial(min(r, n-r));   // and save in another register until later
   eax = max(r,n-r) + 1;               // prod

   xor  %edx, %edx        # in case we skip the mul
   cmp  n, %eax
   jae  endprod           # loop might need to run 0 times, but n==r case already handled

   lea  1(%eax), %ecx     # i = low+1.  Not overflow-checked
   jmp  loop_entry

 prod_loop:                  # do{
    imul  %ecx, %eax         # prod *= i for i=[max+2 .. n-1]
     jo  overflow_in_prod    # maybe need mul to avoid spurious signed but not unsigned overflow cases
    inc   %ecx
  loop_entry:
    cmp   n, %ecx
    jb    prod_loop          # }while(i<n)
                           # leave the loop with ECX = n, with one multiply left to do
    mul   %ecx             # EDX:EAX = n * EAX
    # We're keeping the full 64-bit result, therefore this can't overflow
 endprod:

    div   divisor          # prod /= the smaller factorial
    # EDX:EAX / divisor, quotient in EAX.  Or will raise #DE if it didn't fit.
    ret

overflow_in_prod:
   do something
   ret

未经测试并且没有仔细考虑的问题,可能是循环设置/边界中的一个错误/异常案例

这就是我正在描述的事情:我们可以在累加产品时检查溢出,除了最后一步,我们可以产生64位结果。

在某些情况下,prod循环的最后一个imul会产生高位设置的32位无符号结果,但没有无符号溢出。使用imul / jo会错误地将其检测为溢出,因为它是 signed 溢出的。最后的div不会溢出。因此,如果您不仅关心速度,还可以在那里使用(稍微慢一些)mul

无论如何,这使我们可以处理C(18, 9),其中prod(10 .. 18)= 0x41b9e4200。最后一个指令将生成EAX = 0x3a6c5900,它适合32位,而最后一个mul将其乘以18以产生EDX:EAX = 0x41b9e4200(35个有效位)。然后,将其除以9! = 0x58980,得到EAX = 0xbdec。

nr很大时,EDX:EAX中的有效位数甚至可以更大(但要紧靠在一起,这样我们仍然可以避免溢出)。它们必须相距足够远,以使(n-r)!除数足够大,以使最终结果回落到32位。

否则,您将需要扩展精度除法...