Должен ли __finally запускаться после EXCEPTION_CONTINUE_SEARCH?
В следующем коде функция foo
называет себя рекурсивно один раз. Внутренний вызов вызывает нарушение прав доступа. Внешний вызов ловит исключение.
#include <windows.h>
#include <stdio.h>
void foo(int cont)
{
__try
{
__try
{
__try
{
if (!cont)
*(int *)0 = 0;
foo(cont - 1);
}
__finally
{
printf("inner finally %d\n", cont);
}
}
__except (!cont? EXCEPTION_CONTINUE_SEARCH: EXCEPTION_EXECUTE_HANDLER)
{
printf("except %d\n", cont);
}
}
__finally
{
printf("outer finally %d\n", cont);
}
}
int main()
{
__try
{
foo(1);
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
printf("main\n");
}
return 0;
}
Ожидаемый результат здесь должен быть
inner finally 0
outer finally 0
inner finally 1
except 1
outer finally 1
Тем не мение, outer finally 0
заметно отсутствует в реальном выходе. Это ошибка или есть какая-то деталь, которую я пропускаю?
Для полноты, происходит с VS2015, компиляция для x64. Удивительно, но на x86 такого не происходит, что наводит меня на мысль, что это действительно ошибка.
1 ответ
Существует и более простой пример (мы можем удалить внутренний try/finally
блок:
void foo(int cont)
{
__try
{
__try
{
if (!cont) *(int *)0 = 0;
foo(cont - 1);
}
__except (cont? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH)
{
printf("except %d\n", cont);
}
}
__finally
{
printf("finally %d\n", cont);
}
}
с выходом
except 1
finally 1
так finally 0
блок не выполнен. но в нерекурсивном случае - без ошибок:
__try
{
foo(0);
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
printf("except\n");
}
выход:
finally 0
except
это ошибка в следующей функции
EXCEPTION_DISPOSITION
__C_specific_handler (
_In_ PEXCEPTION_RECORD ExceptionRecord,
_In_ PVOID EstablisherFrame,
_Inout_ PCONTEXT ContextRecord,
_Inout_ PDISPATCHER_CONTEXT DispatcherContext
);
старая реализация этой функции с ошибкой здесь:
//
// try/except - exception filter (JumpTarget != 0).
// After the exception filter is called, the exception
// handler clause is executed by the call to unwind
// above. Having reached this point in the scan of the
// scope tables, any other termination handlers will
// be outside the scope of the try/except.
//
if (TargetPc == ScopeTable->ScopeRecord[Index].JumpTarget) { // bug
break;
}
если у нас установлены последние компиляторы / библиотеки VC, ищите chandler.c
(в моей установке находится в \VC\crt\src\amd64\chandler.c
)
и в файле теперь можно посмотреть следующий код:
if (TargetPc == ScopeTable->ScopeRecord[Index].JumpTarget
// Terminate only when we are at the Target frame;
// otherwise, continue search for outer finally:
&& IS_TARGET_UNWIND(ExceptionRecord->ExceptionFlags)
) {
break;
}
поэтому добавлено дополнительное условие IS_TARGET_UNWIND(ExceptionRecord->ExceptionFlags)
которые исправляют эту ошибку
__C_specific_handler
реализовано в разных библиотеках crt (в некоторых случаях со статической ссылкой, в некоторых случаях будет импортировано из vcruntime*.dll
или же msvcrt.dll
(был отправлен ntdll.dll
)). также ntdll.dll
экспортируйте эту функцию - однако в последних сборках win10 (14393) она все еще не исправлена