Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
Dieser Artikel hilft Ihnen, das Problem zu beheben, bei dem eine mit der C Run-time Library (CRT) verknüpfte C++-DLL einen schwerwiegenden Fehler beim Beenden des Threads verursacht, wenn die DLL-Lade- oder Entladesequenz durch eine unbehandelte Ausnahme unterbrochen wird.
Originalproduktversion: Visual C++
Ursprüngliche KB-Nummer: 2754614
Problembeschreibung
Eine statisch mit der C Run-time Library (CRT) verknüpfte C++-DLL kann einen schwerwiegenden Fehler beim Beenden des Threads verursachen, wenn die DLL-Lade- oder Entladesequenz durch eine unbehandelte Ausnahme unterbrochen wird.
Ein Prozess kann beim Beenden des Threads mit einer Zugriffsverletzungsausnahme (0xC0000005, EXCEPTION_ACCESS_VIOLATION) abstürzen, wenn er dynamisch geladen wurde (z. B. durch Aufrufen von LoadLibraryA()) einer systemeigenen C++-DLL, die statisch mit C-Runtime verknüpft wurde, und die DLL während der Initialisierung oder beim Herunterfahren eine unbehandelte Ausnahme generiert hat.
Während des CRT-Starts oder Herunterfahrens (z. B. während DLL_PROCESS_ATTACH
oder DLL_PROCESS_DETACH
in DllMain()
oder im Konstruktor oder Destruktor eines globalen/statischen C++-Objekts), wenn die DLL einen schwerwiegenden Fehler generiert, der nicht behandelt wird, verschluckt der LoadLibrary
Aufruf einfach die Ausnahme und gibt mit NULL zurück. Wenn die DLL beim Laden oder Entladen fehlschlägt, sind einige Fehlercodes aufgeführt, die Sie möglicherweise beobachten:
- ERROR_NOACCESS (998) oder EXCEPTION_ACCESS_VIOLATION (0xC0000005, 0n3221225477)
- EXCEPTION_INT_DIVIDE_BY_ZERO (0xC0000094, 0n3221225620)
- ERROR_STACK_OVERFLOW (1001) oder EXCEPTION_STACK_OVERFLOW (0xC00000FD, 0n3221225725)
- C++-Ausnahme (0xE06D7363, 0n3765269347)
- ERROR_DLL_INIT_FAILED (0x8007045A)
Dieser Fehler beim Starten oder Herunterfahren der Bibliothek wird in der Regel erst beobachtet, wenn der aufrufende Thread beendet werden soll, in Form einer tödlichen Zugriffsverletzungsausnahme mit einem ähnlichen Aufrufstapel wie unten:
<Unloaded_TestDll.dll>+0x1642 ntdll!RtlProcessFlsData+0x57 ntdll!LdrShutdownProcess+0xbd
ntdll!RtlExitUserProcess+0x74 kernel32!ExitProcessStub+0x12 TestExe!__crtExitProcess+0x17
TestExe!doexit+0x12a TestExe!exit+0x11 TestExe!__tmainCRTStartup+0x11c
kernel32!BaseThreadInitThunk+0xe ntdll!__RtlUserThreadStart+0x70 ntdll!_RtlUserThreadStart+0x1b
Dieses Verhalten kann mit dem folgenden Codeausschnitt in Visual Studio reproduziert werden:
//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 exception
int* pInt = NULL;
*pInt = 5;
break;
}
}
return TRUE;
}
//TestExe.exe:
#include <Windows.h>
#include <stdio.h>
int main(int argc, TCHAR* argv[])
{
HMODULE hModule = LoadLibrary(TEXT("TestDll.dll"));
printf("GetLastError = %d\n", GetLastError());
if (hModule != NULL)
FreeLibrary(hModule);
return 0;
//Access Violation will occur following the above return statement
}
Ursache
Eine FLS-Rückruffunktion (Fiber Local Storage) wird von Windows aufgerufen, wenn der Thread beendet wird, und die Adresse dieser Funktion befindet sich nicht mehr im gültigen Prozessspeicher. Die häufigste Ursache ist die Verwendung statischer CRT in einer DLL, die vorzeitig entladen wird.
Wenn die C-Runtime zur DLL-Ladezeit initialisiert wird, registriert sie eine FLS-Rückruffunktion namens _freefls() über einen Aufruf von FlsAlloc(). Die C-Runtime hebt die Registrierung dieses FLS-Rückrufs jedoch nicht auf, wenn eine unbehandelte Ausnahme in der DLL auftritt, während sie geladen oder entladen wird.
Da die C-Runtime in der DLL statisch verknüpft ist, wird der FLS-Rückruf in dieser DLL selbst implementiert. Wenn diese DLL aufgrund einer unbehandelten Ausnahme nicht geladen oder entladen werden kann, wird die DLL nicht nur automatisch entladen, sondern der FLS-Rückruf der C-Runtime bleibt auch nach dem Entladen der DLL beim Betriebssystem registriert. Wenn der Thread beendet wird (z. B. wenn die EXE-Funktion main()
zurückgibt), versucht das Betriebssystem, die registrierte FLS-Rückruffunktion (_freefls in diesem Fall) aufzurufen, die nun auf nicht zugeordneten Prozessbereich verweist und letztendlich zu einer Access-Verletzungsausnahme führt.
Lösung
Im VC++ 11.0 CRT (in VS 2012) wurde eine Änderung vorgenommen, um die FLS-Rückrufbereinigung bei nicht behandelten Ausnahmen während des DLL-Starts besser zu beheben. Für DLLs, auf deren Quellcode zugegriffen werden kann und daher neu kompiliert werden kann, können die folgenden Optionen ausprobiert werden:
- Kompilieren Sie die DLL mit dem neuesten VC11 CRT (erstellen Sie z. B. die DLL mit VS2012 RTM).
- Verwenden Sie die CRT-DLL anstelle statischer Verknüpfungen mit C-Runtime, während Sie Die DLL kompilieren. verwenden Sie /MD oder /MDd anstelle von /MT oder /MTd.
- Korrigieren Sie nach Möglichkeit die Ursache der unbehandelten Ausnahme, entfernen Sie den ausnahmeanfälligen Codeabschnitt aus
DllMain
, und/oder behandeln Sie die Ausnahme ordnungsgemäß. - Implementieren Sie eine benutzerdefinierte DLL-Einstiegspunktfunktion, um die Initialisierung und den Code von CRT aufzuschließen, um die Registrierung des FLS-Rückrufs des CRT aufzuheben, wenn während des DLL-Starts eine Ausnahme auftritt. Diese Ausnahmebehandlung um den Einstiegspunkt kann ein Problem verursachen, wenn /GS (Puffersicherheitsprüfungen) in einem Debugbuild verwendet wird. Wenn Sie diese Option auswählen, schließen Sie die Ausnahmebehandlung (mithilfe
#if
oder#ifdef
) aus Debugbuilds aus. Für DLLs, die nicht neu erstellt werden können, gibt es derzeit keine Möglichkeit, dieses Verhalten zu korrigieren.
Weitere Informationen
Dieses Verhalten wird durch einen Fehler beim Aufheben der Registrierung eines FLS-Rückrufs in einem Modul verursacht, das entladen wurde, sodass es nicht nur durch eine unbehandelte Ausnahme beim Starten oder Herunterfahren der DLL verursacht wird, sondern auch durch das Einrichten eines FLS-Rückrufs, wie unten dargestellt, und die Registrierung wird nicht aufgehoben, bevor die DLL entladen wird:
//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>
int main(int argc, TCHAR* argv[])
{
HMODULE hModule = LoadLibrary(TEXT("TestDll.dll"));
printf("GetLastError = %d \n", GetLastError());
if (hModule != NULL)
FreeLibrary(hModule);
return 0;
//Access Violation will occur following the above return statement
}
Da die FLS-Rückruffunktion vom Betriebssystem aufgerufen werden soll, um FLS-Bereinigung durchzuführen, führt der obige ungültige Funktionszeiger zu einer Ausnahme von Zugriffsverletzung. Daher würde die ideale Lösung für dieses Problem darin besteht, den Code selbst zu korrigieren, um sicherzustellen, dass der FLS-Rückruf vor dem Entladen der DLL aufgehoben wird.
Notiz
Möglicherweise sind Drittanbieterprodukte auf dem Laufzeitcomputer registriert, die DLLs zur Laufzeit in die meisten Prozesse einfügen. In solchen Fällen kann eine betroffene DLL außerhalb Ihrer Produktentwicklung zu diesem Fehler während des Thread-Exits führen. Wenn Sie nicht in der Lage sind, solche DLLs gemäß den oben vorgeschlagenen Anleitungen neu zu erstellen, kann Ihre einzige Option sein, sich an den Anbieter des Produkts zu wenden und einen solchen Fix anzufordern oder das Drittanbieterprodukt zu deinstallieren.