Восстановление контекста после обработки исключения работает только с включенными VS /RTC (проверки времени выполнения)

Моя конечная цель здесь - предоставить средства для перехвата исключения с плавающей запятой, печати трассировки стека и возобновления выполнения с отключенными исключениями с плавающей запятой (с использованием полученных значений не конечного числа / не числа). Я немного продвинулся со времени моего первоначального вопроса, в котором я понял, что есть больше регистров, которые необходимо настроить, чтобы очистить / настроить модуль с плавающей запятой при использовании SSE (по умолчанию в x64).

Я получил работающий очень простой пример, однако для x64 все рушится, как только я перехожу к релизной сборке. Сборка отладки / выпуска работает нормально против цели x86. Я сузил проблему до опции "Проверка времени выполнения" в Visual-Studio, в частности, RTC, которая "Включает проверку ошибок во время выполнения стека".

Вот пример программы:

#include "stdafx.h"
#include <float.h>
#include <Windows.h>
#include <xmmintrin.h>

double zero = 0.0;

void printException(EXCEPTION_POINTERS * ExceptionInfo){
    bool bFloatingPointRecoverFlag = false;
    switch(ExceptionInfo->ExceptionRecord->ExceptionCode)
    {
        case EXCEPTION_ACCESS_VIOLATION:
            fputs(" EXCEPTION_ACCESS_VIOLATION\n", stderr);
            break;
        case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
            fputs(" EXCEPTION_ARRAY_BOUNDS_EXCEEDED\n", stderr);
            break;
        case EXCEPTION_BREAKPOINT:
            fputs(" EXCEPTION_BREAKPOINT\n", stderr);
            break;
        case EXCEPTION_DATATYPE_MISALIGNMENT:
            fputs(" EXCEPTION_DATATYPE_MISALIGNMENT\n", stderr);
            break;
        case EXCEPTION_FLT_DENORMAL_OPERAND:
            fputs(" EXCEPTION_FLT_DENORMAL_OPERAND\n", stderr);
            break;
        case EXCEPTION_FLT_DIVIDE_BY_ZERO:
            fputs(" EXCEPTION_FLT_DIVIDE_BY_ZERO\n", stderr);
            break;
        case EXCEPTION_FLT_INEXACT_RESULT:
            fputs(" EXCEPTION_FLT_INEXACT_RESULT\n", stderr);
            break;
        case EXCEPTION_FLT_INVALID_OPERATION:
            fputs(" EXCEPTION_FLT_INVALID_OPERATION\n", stderr);
            break;
        case EXCEPTION_FLT_OVERFLOW:
            fputs(" EXCEPTION_FLT_OVERFLOW\n", stderr);
            break;
        case EXCEPTION_FLT_STACK_CHECK:
            fputs(" EXCEPTION_FLT_STACK_CHECK\n", stderr);
            break;
        case EXCEPTION_FLT_UNDERFLOW:
            fputs(" EXCEPTION_FLT_UNDERFLOW\n", stderr);
            bFloatingPointRecoverFlag = 1;
            break;
        case EXCEPTION_ILLEGAL_INSTRUCTION:
            fputs(" EXCEPTION_ILLEGAL_INSTRUCTION\n", stderr);
            break;
        case EXCEPTION_IN_PAGE_ERROR:
            fputs(" EXCEPTION_IN_PAGE_ERROR\n", stderr);
            break;
        case EXCEPTION_INT_DIVIDE_BY_ZERO:
            fputs(" EXCEPTION_INT_DIVIDE_BY_ZERO\n", stderr);
            break;
        case EXCEPTION_INT_OVERFLOW:
            fputs(" EXCEPTION_INT_OVERFLOW\n", stderr);
            break;
        case EXCEPTION_INVALID_DISPOSITION:
            fputs(" EXCEPTION_INVALID_DISPOSITION\n", stderr);
            break;
        case EXCEPTION_NONCONTINUABLE_EXCEPTION:
            fputs(" EXCEPTION_NONCONTINUABLE_EXCEPTION\n", stderr);
            break;
        case EXCEPTION_PRIV_INSTRUCTION:
            fputs(" EXCEPTION_PRIV_INSTRUCTION\n", stderr);
            break;
        case EXCEPTION_SINGLE_STEP:
            fputs(" EXCEPTION_SINGLE_STEP\n", stderr);
            break;
        case EXCEPTION_STACK_OVERFLOW:
            fputs(" EXCEPTION_STACK_OVERFLOW\n", stderr);
            break;
        default:
            fputs(" Unrecognized Exception\n", stderr);
            break;
    }
}

LONG WINAPI myfunc(EXCEPTION_POINTERS * ExceptionInfo){
    printf("#########Caught Ya:");
    printException(ExceptionInfo);
    printf("ExceptionAddr = 0x%p\n",ExceptionInfo->ExceptionRecord->ExceptionAddress);

    /* clear the exception */
    unsigned int stat = _clearfp();

    /* disable fp exceptions*/
    unsigned int ctrlwrd;
    errno_t err =  _controlfp_s(&ctrlwrd, _MCW_EM, _MCW_EM);

    /* Disable and clear the exceptions in the exception context */
    #if _WIN64
        /* Get current context to get the values of MxCsr register, which was
         * set by the calls to _controlfp above, we need to copy these into
         * the exception context so that exceptions really stay disabled.
         * References:
         *    https://msdn.microsoft.com/en-us/library/yxty7t75.aspx
         *    https://software.intel.com/en-us/articles/x87-and-sse-floating-point-assists-in-ia-32-flush-to-zero-ftz-and-denormals-are-zero-daz
         */
        _CONTEXT myContext;
        GetThreadContext(GetCurrentThread(),&myContext);

        ExceptionInfo->ContextRecord->FltSave.ControlWord = ctrlwrd;
        ExceptionInfo->ContextRecord->FltSave.StatusWord = 0;
        ExceptionInfo->ContextRecord->FltSave.MxCsr = myContext.FltSave.MxCsr;
        ExceptionInfo->ContextRecord->FltSave.MxCsr_Mask = myContext.FltSave.MxCsr_Mask;
        ExceptionInfo->ContextRecord->MxCsr = myContext.MxCsr;
    #else
        ExceptionInfo->ContextRecord->FloatSave.ControlWord = ctrlwrd;
        ExceptionInfo->ContextRecord->FloatSave.StatusWord = 0;
    #endif

    return EXCEPTION_CONTINUE_EXECUTION;
}

int _tmain(int argc, _TCHAR* argv[])
{
    double a;
    double b;
    double c;
    double d;
    double e;

    /* do something so that zero can't get optimized */
    if(argc > 999999){
        zero = (double) argc;
    }


    /* Enable fp exceptions */
    _controlfp_s(0, 0, _MCW_EM);

    /* Setup our unhandled exception filter */
   SetUnhandledExceptionFilter(myfunc);

    b = 5.0+zero;

    /* do something bad */
    a = 5.0 / zero;

    c = a * b;

    e = 5.0 / zero;

    d = 4.0 + e;

    printf("a = %f\n",a);
    printf("b = %f\n",b);
    printf("c = %f\n",c);
    printf("d = %f\n",d);
    printf("e = %f\n",e);

    return 0;
}

С включенными RTC этот код создает вывод (что я и ожидаю):

#########Caught Ya: EXCEPTION_FLT_DIVIDE_BY_ZERO
ExceptionAddr = 0x000000013F7A1638
a = 1.#INF00
b = 5.000000
c = 1.#INF00
d = 1.#INF00
e = 1.#INF00

С отключенными RTC этот код создает вывод:

#########Caught Ya: EXCEPTION_FLT_DIVIDE_BY_ZERO
ExceptionAddr = 0x000000013F0415F2
#########Caught Ya: EXCEPTION_ACCESS_VIOLATION
ExceptionAddr = 0x000000007711B519
#########Caught Ya: EXCEPTION_ACCESS_VIOLATION
ExceptionAddr = 0x000000007711B519
#########Caught Ya: EXCEPTION_ACCESS_VIOLATION
ExceptionAddr = 0x000000007711B519
.... repeat forever

Итак, в итоге:

  • против цели x86 (WIN32): нет проблем ни с отладкой, ни с выпуском сборки!

  • против цели x64 (WIN64): нарушение прав доступа происходит после попытки восстановления из исключения с плавающей запятой, только когда ОТКЛ.

Любые мысли о том, что RTC делает, и почему это повлияет на поведение восстановления после исключения с плавающей запятой?

РЕДАКТИРОВАТЬ: я отладил это немного дальше в сборке. Нарушение происходит после возврата из функции фильтра, но до возобновления при делении на ноль. Ниже приведена сборка, приводящая к нарушению прав доступа (последняя строка сборки - виновник):

000000007711B2EF  mov         dword ptr [rcx+0F0h],edi  
000000007711B2F5  fxsave      [rcx+100h]  
000000007711B2FC  movaps      xmmword ptr [rcx+1A0h],xmm0  
000000007711B303  movaps      xmmword ptr [rcx+1B0h],xmm1  
000000007711B30A  movaps      xmmword ptr [rcx+1C0h],xmm2  
000000007711B311  movaps      xmmword ptr [rcx+1D0h],xmm3  
000000007711B318  movaps      xmmword ptr [rcx+1E0h],xmm4  
000000007711B31F  movaps      xmmword ptr [rcx+1F0h],xmm5  
000000007711B326  movaps      xmmword ptr [rcx+200h],xmm6  
000000007711B32D  movaps      xmmword ptr [rcx+210h],xmm7  
000000007711B334  movaps      xmmword ptr [rcx+220h],xmm8  
000000007711B33C  movaps      xmmword ptr [rcx+230h],xmm9  
000000007711B344  movaps      xmmword ptr [rcx+240h],xmm10  
000000007711B34C  movaps      xmmword ptr [rcx+250h],xmm11  
000000007711B354  movaps      xmmword ptr [rcx+260h],xmm12  
000000007711B35C  movaps      xmmword ptr [rcx+270h],xmm13  
000000007711B364  movaps      xmmword ptr [rcx+280h],xmm14  
000000007711B36C  movaps      xmmword ptr [rcx+290h],xmm15  
000000007711B374  stmxcsr     dword ptr [rcx+34h]  
000000007711B378  mov         rax,qword ptr [rsp+8]  
000000007711B37D  mov         qword ptr [rcx+0F8h],rax  
000000007711B384  mov         eax,dword ptr [rsp]  
000000007711B387  mov         dword ptr [rcx+44h],eax  
000000007711B38A  mov         dword ptr [rcx+30h],10000Fh  
000000007711B391  add         rsp,8  
000000007711B395  ret  
000000007711B396  int         3  
000000007711B397  int         3  
000000007711B398  int         3  
000000007711B399  int         3  
000000007711B39A  int         3  
000000007711B39B  int         3  
000000007711B39C  nop         dword ptr [rax]  
000000007711B39F  push        rbp  
000000007711B3A0  push        rsi  
000000007711B3A1  push        rdi  
000000007711B3A2  sub         rsp,30h  
000000007711B3A6  mov         rbp,rsp  
000000007711B3A9  test        rdx,rdx  
000000007711B3AC  je          000000007711B4E4  
000000007711B3B2  cmp         dword ptr [rdx],80000029h  
000000007711B3B8  jne         000000007711B3C4  
000000007711B3BA  cmp         dword ptr [rdx+18h],1  
000000007711B3BE  jae         000000007711B634  
000000007711B3C4  cmp         dword ptr [rdx],80000026h  
000000007711B3CA  jne         000000007711B4E4  
000000007711B3D0  mov         rax,qword ptr [rdx+20h]  
000000007711B3D4  mov         r8,qword ptr [rax+8]  
000000007711B3D8  mov         qword ptr [rcx+90h],r8  
000000007711B3DF  mov         r8,qword ptr [rax+10h]  
000000007711B3E3  mov         qword ptr [rcx+98h],r8  
000000007711B3EA  mov         r8,qword ptr [rax+18h]  
000000007711B3EE  mov         qword ptr [rcx+0A0h],r8  
000000007711B3F5  mov         r8,qword ptr [rax+20h]  
000000007711B3F9  mov         qword ptr [rcx+0A8h],r8  
000000007711B400  mov         r8,qword ptr [rax+28h]  
000000007711B404  mov         qword ptr [rcx+0B0h],r8  
000000007711B40B  mov         r8,qword ptr [rax+30h]  
000000007711B40F  mov         qword ptr [rcx+0D8h],r8  
000000007711B416  mov         r8,qword ptr [rax+38h]  
000000007711B41A  mov         qword ptr [rcx+0E0h],r8  
000000007711B421  mov         r8,qword ptr [rax+40h]  
000000007711B425  mov         qword ptr [rcx+0E8h],r8  
000000007711B42C  mov         r8,qword ptr [rax+48h]  
000000007711B430  mov         qword ptr [rcx+0F0h],r8  
000000007711B437  mov         r8,qword ptr [rax+50h]  
000000007711B43B  mov         qword ptr [rcx+0F8h],r8  
000000007711B442  mov         r8d,dword ptr [rax+58h]  
000000007711B446  mov         dword ptr [rcx+34h],r8d  
000000007711B44A  mov         dword ptr [rcx+118h],r8d  
000000007711B451  mov         r8w,word ptr [rax+5Ch]  
000000007711B456  mov         word ptr [rcx+100h],r8w  
000000007711B45E  movaps      xmm0,xmmword ptr [rax+60h]  
000000007711B462  movaps      xmmword ptr [rcx+200h],xmm0  
000000007711B469  movaps      xmm0,xmmword ptr [rax+70h]  
000000007711B46D  movaps      xmmword ptr [rcx+210h],xmm0  
000000007711B474  movaps      xmm0,xmmword ptr [rax+80h]  
000000007711B47B  movaps      xmmword ptr [rcx+220h],xmm0  
000000007711B482  movaps      xmm0,xmmword ptr [rax+90h]  
000000007711B489  movaps      xmmword ptr [rcx+230h],xmm0  
000000007711B490  movaps      xmm0,xmmword ptr [rax+0A0h]  
000000007711B497  movaps      xmmword ptr [rcx+240h],xmm0  
000000007711B49E  movaps      xmm0,xmmword ptr [rax+0B0h]  
000000007711B4A5  movaps      xmmword ptr [rcx+250h],xmm0  
000000007711B4AC  movaps      xmm0,xmmword ptr [rax+0C0h]  
000000007711B4B3  movaps      xmmword ptr [rcx+260h],xmm0  
000000007711B4BA  movaps      xmm0,xmmword ptr [rax+0D0h]  
000000007711B4C1  movaps      xmmword ptr [rcx+270h],xmm0  
000000007711B4C8  movaps      xmm0,xmmword ptr [rax+0E0h]  
000000007711B4CF  movaps      xmmword ptr [rcx+280h],xmm0  
000000007711B4D6  movaps      xmm0,xmmword ptr [rax+0F0h]  
000000007711B4DD  movaps      xmmword ptr [rcx+290h],xmm0  
000000007711B4E4  mov         eax,dword ptr [rcx+30h]  
000000007711B4E7  and         eax,100040h  
000000007711B4EC  cmp         eax,100040h  
000000007711B4F1  jne         000000007711B519  
000000007711B4F3  mov         r8d,dword ptr [rcx+34h]  
000000007711B4F7  movsxd      rax,dword ptr [rcx+4E0h]  
000000007711B4FE  lea         rbx,[rcx+2D0h]  
000000007711B505  add         rbx,rax  
000000007711B508  xchg        r8d,dword ptr [rbx+18h]  
000000007711B50C  mov         eax,0FFFFFFFCh  
000000007711B511  cdq  
000000007711B512  xrstor      [rbx]  
000000007711B515  mov         dword ptr [rbx+18h],r8d  
000000007711B519  fxrstor     [rcx+100h]  

Значение в отладчике "rcx" равно 0x30e340, а сообщение об исключении в visual studio гласит: "Исключение первого шанса в 0x7711b519 в fptest.exe: 0xC0000005: расположение чтения нарушения доступа 0xffffffffffffffff".

Почему VS сообщает, что пытается прочитать 0xffffffffffffffff?

1 ответ

Решение

Наконец-то получил ответ на свой вопрос. Я использовал линию GetThreadContext(GetCurrentThread(),&myContext) для захвата текущего контекста, который имел желаемые значения для регистров с плавающей запятой после вызова _clearfp и _controlfp. Однако я не заметил в справке GetThreadContext: если вы вызываете GetThreadContext для текущего потока, функция успешно завершается; однако возвращенный контекст недопустим.

Оказывается, правильным способом получения текущего контекста потока является RtlCaptureContext. Я отредактирую свой оригинальный код, чтобы отразить.

Другие вопросы по тегам