代码之家  ›  专栏  ›  技术社区  ›  Neitsa

自然生成`EXCEPTION_FLT_DEFNORMAL_OPERAND`异常

  •  1
  • Neitsa  · 技术社区  · 5 年前

    问题

    我怎么能 自然地 没有 RaiseException )生成 EXCEPTION_FLT_DENORMAL_OPERAND Windows上的异常(最好是x64)?

    上下文

    我们有一个程序,可以捕捉崩溃的程序,回放错误并分析错误的原因。它比简单的调试器复杂得多,因为它可以回放崩溃,污染参数传播的内存,执行一些一致性和其他复杂的事情。

    为此,我们需要在各种崩溃的情况下测试程序,即生成 Windows exceptions .

    我知道我可以打电话 RaiseException 具有所需的 dwExceptionCode 但在我们的案例中,这与“自然”提出的相同例外非常不同。在我们的案例中,生成 EXCEPTION_fillt_deormal_operator 同时操作浮点数。

    EXCEPTION_fillt_deormal_operator

    根据文件,如果出现以下情况,则提出例外情况:

    浮点运算中的一个操作数是非规格化的。非正规值是指太小而无法表示为标准浮点值的值。

    解释了非正规(也称为亚正规)值 thoroughly on Wikipedia ; 使用一些“古怪”的C(C++)代码生成它们也很容易:

        const unsigned ux = 0xf; // denormal as float.
        const unsigned uy = 0x7; // denormal as float.
        
        //auto x = (float)ux; // this obviouly doesn't work as you end up with 15.f.
    
        auto px = (float*)&ux;
        auto py = (float*)&uy;
        
        auto x = *px; // denormal
        auto y = *py; // denormal
    

    注意,我可以在调试器下看到 x 和 y 是非正规的。

    该代码是使用为x64体系结构编译的 cl 编译器和此函数:

    DWORD ExceptionFltDenormalOperand(LPVOID lpParam)
    {
        UNREFERENCED_PARAMETER(lpParam);
    
        _control87(_EM_INVALID, _EM_DENORMAL);
    
        const unsigned ux = 0xf; // denormal as float.
        const unsigned uy = 0x7; // denormal as float.
    
        auto px = (float*)&ux;
        auto py = (float*)&uy;
    
        auto x = *px;
        auto y = *py;
        //auto y = 1.f;
    
        float sum = 0.0f;
        for (int i = 0; i < 1000; i++) {
            sum += x / y;
        }
    
        return  static_cast<DWORD>(sum); // avoid optimizing away the above computation.
    }
    

    相关编译器标志:

    请注意,分解后的代码显示 XMM 使用(SIMD)寄存器,而不是x87 FPU( ST(x) 寄存器);下面是分部 x / y :

    00007FF7B46F1AF5     | F3:0F1085 C4000000       | movss xmm0,dword ptr ss:[rbp+C4]
    00007FF7B46F1AFD     | F3:0F5E85 E4000000       | divss xmm0,dword ptr ss:[rbp+E4]
    

    控制异常

    这个 _control87 函数用于控制浮点控制字的行为,尤其是用于非正规操作数。

    _control87和_controlfp之间的区别在于它们如何处理DENORMAL值。对于x86、x64、ARM和ARM64平台,_control87可以设置和清除DENORMAL OPERAND异常掩码_controlfp不修改DENORMAL OPERAND异常掩码。

    该函数的用途如下:

        _control87(_EM_INVALID, _EM_DENORMAL);
    

    调试时:

    // Before: MxCsr = 0x1F80
    
    00007FF7B46F1A6A    | FF15 E0190100    | call qword ptr ds:[<&_control87>]
    
    // After: MxCsr = 0x1E80
    
    

    根据《英特尔手册》,关于 MxCsr 登记

    位7到12为SIMD浮点异常提供单独的掩码位。如果设置了相应的掩码位,则会屏蔽异常类型,如果清除了该位,则不屏蔽异常类型。这些掩码位是在加电或复位时设置的。这会导致最初屏蔽所有SIMD浮点异常。

    MxCsr register

    默认情况下,该值为0x1F80(所有遮罩都设置为1)。值0x1E80表示比特8为0(其中比特8为DM或 Denormal Operation Mask ):

    0x1E80 in binary.
    
     _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
    |3|3|2|2|2|2|2|2|2|2|2|2|1|1|1|1|1|1|1|1|1|1|0|0|0|0|0|0|0|0|0|0|
    |1|0|9|8|7|6|5|4|3|2|1|0|9|8|7|6|5|4|3|2|1|0|9|8|7|6|5|4|3|2|1|0|
    |‾|‾|‾|‾|‾|‾|‾|‾|‾|‾|‾|‾|‾|‾|‾|‾|‾|‾|‾|‾|‾|‾|‾|‾|‾|‾|‾|‾|‾|‾|‾|‾|
    |0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|1|1|1|1|0|1|0|0|0|0|0|0|0|
     ‾ ‾ ‾ ‾ ‾ ‾ ‾ ‾ ‾ ‾ ‾ ‾ ‾ ‾ ‾ ‾ ‾ ‾ ‾ ‾ ‾ ‾ ‾ ‾ ‾ ‾ ‾ ‾ ‾ ‾ ‾ ‾
    

    后果

    (执行上述代码的)结果异常 总是 导致 EXCEPTION_FLT_INVALID_OPERATION (而不是等待 EXCEPTION_fillt_deormal_operator ).

    [edit]在异常站点 MxCsr 寄存器值为0x1E82;这表明比特1(DE:Denormal Flag)被切换。

    其他测试

    • 由于异常描述专门指 One of the operands in a floating-point operation is denormal. ,只使用一个非正规化操作数(而不是示例中的两个)会显示相同的结果。

    • 有可能 /fp:除 不会改变结果。

    • 有可能 /fp:strict 不会改变结果。

    0 回复  |  直到 5 年前