克服x86 idiv #DE异常

时间:2016-05-07 06:10:22

标签: assembly x86 try-catch overflow

Re:x86汇编语言 -

我有三个32位有符号数:n1,n2和n3。

我想通过n2来确定n1以获得64位签名结果。

然后我想通过n3想象64位结果。

问题是如果64位带符号的结果足够大和/或如果n3足够小,将导致溢出并且idiv将抛出#DE异常。

如果idiv只是在溢出时设置#DE,我可以检查确认 ((n1 * n2)/ n3)* n3 +((n1 * n2)mod n3)=(n1 * n2)。如果没有,会发生溢出,我可以相应地继续。

但#DE与其他人并不好玩。当它被提出时,它报告"程序已经停止工作"然后把你踢出去。

所以我需要找到一些方法来预先检查idiv是否会在我进行除法之前导致溢出,或者我需要在汇编语言中执行相当于try ... catch的操作。

我已经搜索过互联网(包括这里),但总的来说并没有找到相关信息。没什么特别有用的。

我已尝试在c ++中尝试内联代码尝试...无济于事 - 它仍然报告"程序已停止工作"然后把你踢出去。

例如,使用两个支持文件:

// TC.h
// Version 1.0.0
// MDJ 2016/05/06

extern "C" int q;
extern "C" int m;

// TC.s
// Version 1.0.0
// MDJ 2016/05/06

    .globl _q
    .globl _m

    .bss
    .align 4
_q:
   .space 4

_m:
    .space 4

此文件运行完成并生成正确的结果:

// TryCatch.cpp
// Version 1.0.0
// MDJ 2016/05/06

#include <iostream>
#include "TC.h"

using namespace std;

int main() {

    cout << endl;

    try {

        # AT&T syntax
        asm(
            "movl       $34,    %eax\n\t"
            "movl       $48,    %edx\n\t"
            "imull  %edx\n\t"
            "movl       $13,    %ecx\n\t"
            "idivl  %ecx\n\t"
            "movl       %eax,   _q\n\t"
            "movl       %edx,   _m\n\t"
        );
    }
    catch(int e) {
        cout << "Caught." << endl;
    }

    cout << "Reached the End." << endl;
    cout << "q = " << q << endl;
    cout << "m = " << m << endl;
    cout << endl;

    return 0;
}

但是,如果我像这样改变n1,n2和n3:

// TryCatch.cpp
// Version 1.0.0
// MDJ 2016/05/06

#include <iostream>
#include "TC.h"

using namespace std;

int main() {

    cout << endl;

    try {

        # AT&T syntax
        asm(
            "movl       $234567890, %eax\n\t"
            "movl       $123456789, %edx\n\t"
            "imull  %edx\n\t"
            "movl       $3, %ecx\n\t"
            "idivl  %ecx\n\t"
            "movl       %eax,   _q\n\t"
            "movl       %edx,   _m\n\t"
        );
    }
    catch(int e) {
        cout << "Caught." << endl;
    }

    cout << "Reached the End." << endl;
    cout << "q = " << q << endl;
    cout << "m = " << m << endl;
    cout << endl;

    return 0;
}

&#34; catch&#34;没有收到溢出,系统反而报告&#34;程序已停止工作&#34;然后把你踢出去。

任何帮助都将不胜感激。

2 个答案:

答案 0 :(得分:1)

我突然想到我完全走错了轨道(作为模型铁路运输车,这是一个真正令人发指的罪行)双关语意图: - )。

但是,真的,我一直在努力解决这个问题。

相反,我应该采取简单的方法:我应该回到我1950年的文法学校和我长期分裂的第一次冒险。

不要对EDX感到困惑:EAX被ECX分割,让我们想到一个两位数(无符号)数字除以一位数(无符号)数字。

现在,两位数字是被除数,它有一位数和十位数。所以它可以在0到99之间变化。

并且,一位数字是除数,它只有一位数。因此,它可以在1到9之间变化(因为不允许除以零)。

例如,考虑77除以2:

                            3 8
                           _____
                        2 | 7 7
                            6
                            _
                            1 7
                            1 6
                            ___
                              1

因此,结果是:商为38,余数为1.

但是,在这里,与股息一样,我们允许商也有两位数:十位数和一位数。如果我们改为将商限制为只有一位数,会发生什么。

然后我们可以调用任何除法,这导致商数在十位数字段中具有除零之外的任何数字,AN OVERFLOW !!!

但是,那么,产生这样一个溢出所需的条件是什么:任何一个小于或等于股息数字的DIVISOR !!!

类似地,在EDX的划分:ECX的EAX中,如果ECX&lt; = EDX !!!

将发生溢出

因此,这是我们对溢出的简单测试:

                        ECX <= EDX

适用于无符号分割。

预先检查有符号的除法溢出要复杂得多。我认为这会奏效,但我还在测试。

从EDX中的64位带符号被除数开始:EAX和ECX中的32位带符号除数。然后:

  # Temporarily save the dividend
  movl  %edx, _dividendHigh                     # Most-significant 32b
  movl  %eax, _dividendLow                      # Least-significant 32b

  # Check the divisor for zero
  testl %ecx, %ecx                              # Is divisor = 0 ?
  jz    _DivideByZero                           # Go if Yes

  # Check the divisor for +/- 1
  cmpl  $1, %ecx
  je    _dChkA                                  # Go if divisor =  1
  cmpl  $-1,    %ecx
  je    _dChkA                                  # Go if divisor = -1
  jmp   _dChkC                                  # Else continue

_dChkA:
  # If dividendHigh < -1 or > 0 and divisor = +/- 1
  #   then overflow will occur.
  cmpl  $-1,        %edx
  jl    _DivideOverflow                         # Go if divHigh < -1
  cmpl  $0,     %edx
  jg    _DivideOverflow                         # Go if divHigh >    0

  # If dividendHigh = -1 and bit 31 of dividendLow = 0
  #   and divisor = +/- 1 then overflow will occur.
  cmpl  $-1,    %edx
  jne   _dChkB                                  # Go if divHigh <>  -1
  bt    $31,    %eax
  jnc   _DivideOverflow                         # Go if divLow b31 = 0

_dChkB:
  # If dividendHigh = 0 and bit 31 of dividendLow = 1
  #   and divisor = +/- 1 then overflow will occur.
  cmpl  $0, %edx
  jne   _dChkC                                  # Go if divHigh <>   0
  bt    $31,    %eax
  jc    _DivideOverflow                         # Go if divLow b31 = 1

  # Check for non-unary overflow
  #   Overflow will occur if the 
  #   most-significant 33b can be
  #   divided by the divisor. NOTE:
  #   that's 33 bits, not 32, 
  #   because all numbers are signed.

  # Do dividend shift and manual sign extension
  # Check bit 63 to determine if dividend is positive or negative
_dChkC: 
  bt    $31,    %edx
  jc    _dChkD                                  # Go if negative

  # Do the 64-bit shift                         # Positive
  # First, shift the Least-significant
  #   32b left 1 bit (bit 32 --> CF).
  shll  $1, %eax

  # Then, rotate the Most-significant
  #   32b left, through the carry, 1 bit
  #   (CF --> bit 1 then bit 32 --> CF).
  rcll  $1, %edx

  # Move it to %eax and manually positive-sign extend it
  movl  %edx,   %eax
  jmp       _dChkE

_dChkD:                                             # Negative  
  # Do the 64-bit shift                                     
  # First, shift the Least-significant
  #   32b left 1 bit (bit 32 --> CF).
  shll  $1, %eax

  # Then, rotate the Most-significant
  #   32b left, through the carry, 1 bit
  #   (CF --> bit 1 then bit 32 --> CF).
  rcll  $1, %edx

  # Move it to %eax and manually negative-sign extend it
  movl  %edx,   %eax
  movl  $-1,    %edx

  # Do the Test Divide of the 
  #   Most-Significant 33b
_dChkE:
  idivl %ecx                                    # EDX:EAX / ECX
                                                #   EAX = test quotient
                                                #   EDX = test remainder
  testl %eax,   %eax
  jnz       _DivideOverflow                     # Go if Quotient <> 0

  # Get the full dividend
  movl  _dividendHigh,  %edx                    # Most-significant 32b
  movl  _dividendLow,   %eax                    # Least-significant 32b

  # Perform the 64b by 32b division
  idivl ecx                                     #   EDX:EAX / ECX
                                                #     EAX = quotient
                                                #     EDX = remainder

答案 1 :(得分:0)

你的DivideTester太荒谬了。您只需要保留来电者%ebx%esi%edi%ebp%esp。您似乎在同一个函数中保存/恢复大量寄存器,并在最后多次恢复相同的寄存器。

试试这个:

.globl _DivideTester
_DivideTester:
# extern "C" void DivideTester(void);
# clobbers eax and edx.  The C compiler will assume this, because the standard calling convention says functions are allowed to clobber eax, ecx, and edx

    # mov    $0,       %edx
    # mov    $6742542, %eax
    # Instead, set the globals from C, and print before calling

    mov    _dividendHigh, %edx        # load from globals
    mov    _dividendLow,  %eax
    # movl    _divisor, %ecx

    idivl   _divisor                  # EDX:EAX / divisor
    mov    %eax, _quotient            #       EAX = Quotient
    mov    %edx, _remainder           #       EDX = Remainder

    # print the results from C, or use a debugger to inspect them
    ret

或者如果你坚持将常量硬编码到asm中,你仍然可以这样做。您仍然可以从C打印它们。

注意这个函数读起来有多容易?除了idiv之外,基本上什么都不会出错。从asm获取函数调用是更多的工作,所以不要浪费你的时间。让编译器做它擅长的事情。你仍然可以通过反汇编/单步执行代码来确切地看到编译器做了什么,所以它不会因为将该部分留给C而失去调试能力。它更像是你避免整个一类错误(就像你最初那样)。

您只需要为mov $1234, _memory之类的内容添加操作数大小,其中没有寄存器来暗示操作数大小。我宁愿省略它。如果不明确,as会向您显示错误消息而不是选择默认消息,因此它始终是安全的。