【问题标题】:How to detect overflow conditions in Assembly Language X86如何检测汇编语言 X86 中的溢出条件
【发布时间】:2012-03-21 01:32:17
【问题描述】:

我有一个任务,我们必须编写两个函数。还必须使用处理器的条件代码检测溢出条件并返回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  

这个函数基本上是做nCr = n! / (r! (n-r)!)的。当数字变大时,会在阶乘中发生溢出。

我只是不明白如何设置溢出条件。

【问题讨论】:

  • 溢出条件由算术指令自动设置。你只需要知道如何阅读它们。
  • 感谢您的回复。我应该放入自己的状态标志。我只是不知道如何放入自己的条件标志。
  • 根据问题和您的评论看来这是作业,所以我添加了作业标签。如果这不是家庭作业,请随意删除标签。
  • @user1282285 您可以通过执行所需的操作来设置条件标志。还是我错过了什么?
  • 如果您自己编写和理解程序集,而不是使用 GCC 生成的程序集,您可能会更轻松。

标签: c assembly x86 integer-overflow


【解决方案1】:

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

2) “JO”(溢出时跳转)和“JNO”(未溢出时跳转)允许您根据是否发生溢出进行分支

3) 您可能只是在“JO”之后将“%eax”设置为 0。

4) 非常棒的资源,如果您还不熟悉的话:

Programming from the Ground Up, Jonathan Bartlett

【讨论】:

  • 非常感谢! JO 非常有帮助。还有资源。
【解决方案2】:

在 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。例如

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

【讨论】:

    【解决方案3】:

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

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

    当商不适合 AL/AX/EAX/RAX(取决于操作数大小)时,除法会引发 #DE 异常。不幸的是,没有办法抑制/屏蔽这一点,所以你不能尝试 2N / N => N 位除法并在事后检测溢出,除非你有一个信号处理程序来捕获 SIGFPE(在 POSIX 操作系统上) .或者在裸机上,#DE 的中断处理程序。

    专门针对组合:

    您可以提前取消并只计算prod(r+1 .. n),而不是天真地计算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) where prod(10 .. 18) = 0x41b9e4200。最后一个 imul 将产生适合 32 位的 EAX = 0x3a6c5900,最后一个 mul 将乘以 18 以产生 EDX:EAX = 0x41b9e4200(35 个有效位)。然后我们将它除以9! = 0x58980 并得到 EAX = 0xbdec。

    nr 很大时,EDX:EAX 中的有效位数可能会更大(但它们靠得很近,所以我们仍然可以避免溢出)。不过,它们之间的距离必须足够远,(n-r)! 除数才能足够大,以使最终结果回落到适合 32 位。

    否则你需要扩展精度除法,这是可能的......

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2023-03-25
      • 1970-01-01
      • 2015-04-05
      • 1970-01-01
      • 2017-01-03
      • 1970-01-01
      • 1970-01-01
      • 2019-02-13
      相关资源
      最近更新 更多