Erro fatal na saída do thread se o retorno de chamada FLS não for liberado
Artigo
Este artigo ajuda você a resolver o problema em que uma DLL C++ vinculada estaticamente à CRT (Biblioteca de Tempo de Execução) C causa um erro fatal na saída do thread se a sequência de carregamento ou descarregamento da DLL for interrompida por uma exceção sem tratamento.
Versão original do produto: Visual C++ Número original do KB: 2754614
Sintomas
Uma DLL C++ vinculada estaticamente à CRT (Biblioteca de Tempo de Execução) C pode causar um erro fatal na saída do thread se a sequência de carregamento ou descarregamento da DLL for interrompida por uma exceção sem tratamento.
Um processo pode falhar na saída do thread com uma exceção de violação de acesso (0xC0000005, EXCEPTION_ACCESS_VIOLATION) se ele tiver carregado dinamicamente (como chamando LoadLibraryA()) uma DLL C++ nativa que foi vinculada estaticamente ao C Runtime e a DLL gerou uma exceção sem tratamento durante sua inicialização ou desligamento.
Durante a inicialização ou desligamento do CRT (como durante DLL_PROCESS_ATTACH ou DLL_PROCESS_DETACH em DllMain(), ou no construtor ou destruidor de um objeto C++ global/estático), se a DLL gerar um erro fatal que não é tratado, a LoadLibrary chamada apenas engole a exceção e retorna com NULL. Quando o carregamento ou descarregamento da DLL falha, alguns códigos de erro que você pode observar incluem:
ERROR_NOACCESS (998) ou EXCEPTION_ACCESS_VIOLATION (0xC0000005, 0n3221225477)
ERROR_STACK_OVERFLOW (1001) ou EXCEPTION_STACK_OVERFLOW (0xC00000FD, 0n3221225725)
Exceção do C++ (0xE06D7363, 0n3765269347)
ERROR_DLL_INIT_FAILED (0x8007045A)
Essa falha de inicialização ou desligamento da biblioteca geralmente não é observada até que o thread de chamada esteja prestes a sair, na forma de uma exceção mortal de violação de acesso com uma pilha de chamadas semelhante à abaixo:
Esse comportamento pode ser reproduzido com o seguinte snippet de código no Visual Studio:
C++
//TestDll.dll: Make sure to use STATIC CRT to compile this DLL (i.e., /MT or /MTd)#include<Windows.h>BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved){
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
{
//About to generate an exceptionint* pInt = NULL;
*pInt = 5;
break;
}
}
return TRUE;
}
//TestExe.exe:#include<Windows.h>#include<stdio.h>intmain(int argc, TCHAR* argv[]){
HMODULE hModule = LoadLibrary(TEXT("TestDll.dll"));
printf("GetLastError = %d\n", GetLastError());
if (hModule != NULL)
FreeLibrary(hModule);
return0;
//Access Violation will occur following the above return statement
}
Causa
Uma função de retorno de chamada FLS (Fiber Local Storage) é invocada pelo Windows quando o thread é encerrado e o endereço dessa função não está mais na memória de processo válida. A causa mais comum é o uso de CRT estático em uma DLL que é descarregada prematuramente.
Quando o C Runtime é inicializado no tempo de carregamento da DLL, ele registra uma função de retorno de chamada FLS chamada _freefls() por meio de uma chamada para FlsAlloc(); no entanto, o C Runtime não cancela o registro desse retorno de chamada FLS se ocorrer uma exceção sem tratamento na DLL enquanto ela está sendo carregada ou descarregada.
Como o C Runtime está vinculado estaticamente na DLL, seu retorno de chamada FLS é implementado nessa própria DLL. Se essa DLL não for carregada ou descarregada devido a uma exceção sem tratamento, não apenas a DLL será descarregada automaticamente, mas o retorno de chamada FLS do C Runtime permanecerá registrado no sistema operacional mesmo depois que a DLL for descarregada. Quando o thread é encerrado (por exemplo, quando a função do EXE main() retorna), o sistema operacional tenta invocar a função de retorno de chamada FLS registrada (_freefls neste caso) que agora aponta para o espaço de processo não mapeado e, por fim, resulta em uma exceção de violação de acesso.
Solução
Uma alteração foi feita no VC++ 11.0 CRT (no VS 2012) para lidar melhor com a limpeza de retorno de chamada FLS em exceções sem tratamento durante a inicialização da DLL. Portanto, para DLLs cujo código-fonte é acessível e, portanto, pode ser recompilado, as seguintes opções podem ser tentadas:
Compile a DLL com o CRT VC11 mais recente (por exemplo, compile a DLL com o VS2012 RTM).
Use a DLL CRT, em vez de vincular estática ao C Runtime ao compilar sua DLL; use /MD ou /MDd em vez de /MT ou /MTd.
Se possível, corrija a causa da exceção sem tratamento, remova a parte do código DllMainpropensa a exceções e/ou trate a exceção corretamente.
Implemente uma função de ponto de entrada de DLL personalizada, encapsulando a inicialização e o código do CRT para cancelar o registro do retorno de chamada FLS do CRT se ocorrer uma exceção durante a inicialização da DLL. Esse tratamento de exceção em torno do ponto de entrada pode causar um problema quando /GS (verificações de segurança do buffer) é usado em um build de depuração. Se você escolher essa opção, exclua o tratamento de exceção (using #if ou #ifdef) das compilações de depuração. Para DLLs que não podem ser recriadas, atualmente não há como corrigir esse comportamento.
Mais informações
Esse comportamento é causado por uma falha ao cancelar o registro de um retorno de chamada FLS em um módulo que foi descarregado, portanto, é causado não apenas por uma exceção não tratada durante a inicialização ou desligamento do CRT da DLL, mas também pela configuração de um retorno de chamada FLS, conforme mostrado abaixo, e não cancelando o registro antes que a DLL seja descarregada:
C++
//TestDll.dll: To reproduce the problem, compile with static CRT (/MT or /MTd)#include<Windows.h>VOID WINAPI MyFlsCallback(PVOID lpFlsData){
}
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved){
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
{
//Leaking FLS callback and rather setting an invalid callback.
DWORD dwFlsIndex = FlsAlloc(MyFlsCallback);
FlsSetValue(dwFlsIndex, (PVOID)5);
break;
}
}
return TRUE;
}
//TestExe.exe:#include<Windows.h>#include<stdio.h>intmain(int argc, TCHAR* argv[]){
HMODULE hModule = LoadLibrary(TEXT("TestDll.dll"));
printf("GetLastError = %d \n", GetLastError());
if (hModule != NULL)
FreeLibrary(hModule);
return0;
//Access Violation will occur following the above return statement
}
Como a função de retorno de chamada FLS deve ser chamada pelo sistema operacional para executar a limpeza FLS, o ponteiro de função inválido acima resultará em exceção de violação de acesso. Portanto, a solução ideal para esse problema seria corrigir o próprio código, garantindo que o retorno de chamada FLS não seja registrado antes que a DLL seja descarregada.
Observação
Pode haver produtos de terceiros registrados no computador de runtime que injetarão DLLs em runtime na maioria dos processos. Nesses casos, uma DLL afetada fora do desenvolvimento do produto pode levar a esse erro durante a saída do thread. Se você não estiver em posição de recompilar essas DLLs de acordo com as diretrizes sugeridas acima, sua única opção poderá ser entrar em contato com o fornecedor do produto e solicitar essa correção ou desinstalar o produto de terceiros.
Este módulo analisa a validação de código usando uma combinação de teste, depuração e tratamento de exceções. O processo de depuração e os benefícios oferecidos pelo depurador de código são analisados, acompanhado das entidades de segurança e dos processos existentes por trás do tratamento de exceções.