Anfügen und Trennen des Profilers
In .NET Framework-Versionen vor .NET Framework, Version 4 mussten eine Anwendung und der entsprechende Profiler zur gleichen Zeit geladen werden. Wenn eine Anwendung gestartet wurde, wurde durch die Laufzeit bestimmt, ob für die Anwendung ein Profil erstellt werden soll, indem Umgebungsvariablen und Registrierungseinstellungen untersucht wurden und anschließend der erforderliche Profiler geladen wurde. Der Profiler verblieb dann im Prozessbereich, bis die Anwendung beendet wurde.
Ab .NET Framework 4 kann ein Profiler auch nach dem Start der Anwendung an die Anwendung angefügt werden, ein Profil für die Anwendung erstellt und anschließend der Profiler vor dem Beenden der Anwendung getrennt werden. Nach dem Trennen des Profilers wird die Anwendung weiterhin so ausgeführt wie vor dem Anfügen des Profilers. Es bleibt also keine Spur davon zurück, dass der Profiler zeitweilig im Prozessbereich vorhanden war.
Ab .NET Framework 4 können zudem mit Anfüge- und Trennmethoden für Profilerstellung und entsprechenden API-Erweiterungen für Profilerstellung profilerbasierte Tools als Just-In-Time verwendbare, standardmäßige Diagnosetools verwendet werden.
Diese Erweiterungen werden in den folgenden Abschnitten beschrieben:
Anfügen eines Profilers
Details zu Anfügevorgängen
Schrittweises Anfügen
Suchen der AttachProfiler-Methode
Anfügen an eine Zielanwendung vor dem Laden der Laufzeit
Einschränkungen für Anfügevorgänge
Beispiel für das Anfügen eines Profilers
Initialisieren des Profilers nach dem Anfügen
Abschließen des Anfügens eines Profilers
Trennen eines Profilers
Threadüberlegungen während des Trennens
Schrittweises Trennen
Einschränkungen für Trennvorgänge
Debuggen
Anfügen eines Profilers
Zwei separate Prozesse sind erforderlich, um einen Profiler an eine ausgeführte Anwendung anzufügen: der Triggerprozess und der Zielprozess.
Der Triggerprozess ist eine ausführbare Datei, die bewirkt, dass die Profiler-DLL in den Prozessbereich einer ausgeführten Anwendung geladen wird. Im Triggerprozess wird mit der ICLRProfiling::AttachProfiler-Methode die Common Language Runtime (CLR) zum Laden der Profiler-DLL aufgefordert. Wenn die Profiler-DLL in den Zielprozess geladen wurde, wird der Triggerprozess möglicherweise weiterhin ausgeführt oder beendet. Dies liegt im Ermessen des Profilerentwicklers.
Der Zielprozess enthält die Anwendung, für die Sie ein Profil erstellen möchten, sowie die CLR. Nach dem Aufruf der AttachProfiler-Methode wird die Profiler-DLL zusammen mit der Zielanwendung in diesen Prozessbereich geladen.
Details zu Anfügevorgängen
AttachProfiler fügt den angegebenen Profiler an den angegebenen Prozess an, wobei optional einige Daten an den Profiler übergeben werden. Es wird auf das angegebene Timeout für den Abschluss des Anfügevorgangs gewartet.
Innerhalb des Zielprozesses ist die Prozedur zum Laden eines Profilers zur Anfügezeit mit der Prozedur zum Laden eines Profilers zur Startzeit vergleichbar: Die CLR CoCreates der angegebenen CLSID fragt die ICorProfilerCallback3-Schnittstelle ab und ruft die ICorProfilerCallback3::InitializeForAttach-Methode auf. Wenn diese Vorgänge innerhalb des zugewiesenen Timeouts erfolgreich sind, gibt AttachProfiler ein S_OK HRESULT zurück. Daher sollte für den Triggerprozess ein Timeout ausgewählt werden, das für die Initialisierung des Profilers ausreichend ist.
Hinweis |
---|
Timeouts können auftreten, da ein Finalizer im Zielprozess länger als der Timeoutwert ausgeführt wurde, was zu HRESULT_FROM_WIN32 (ERROR_TIMEOUT) geführt hat.Wenn dieser Fehler angezeigt wird, kann der Anfügevorgang wiederholt werden. |
Wenn das Timeout vor Abschluss des Anfügevorgangs abläuft, gibt AttachProfiler einen HRESULT-Fehler zurück. Der Anfügevorgang kann dennoch erfolgreich verlaufen sein. In solchen Fällen gibt es alternative Möglichkeiten, den erfolgreichen Verlauf festzustellen. Profilerentwickler verwenden oftmals einen benutzerdefinierten Kommunikationschannel zwischen dem Triggerprozess und der Zielanwendung. Mit einem derartigen Kommunikationschannel kann ein erfolgreicher Anfügevorgang festgestellt werden. Der Triggerprozess kann auch einen erfolgreichen Verlauf erkennen, indem AttachProfiler erneut aufgerufen und CORPROF_E_PROFILER_ALREADY_ACTIVE empfangen wird.
Außerdem muss der Zielprozess ausreichende Zugriffsrechte besitzen, damit die DLL des Profilers geladen werden kann. Dies kann beim Anfügen an Dienste ein Problem sein (z. B. der W3wp.exe-Prozess), die möglicherweise unter Konten mit eingeschränkten Zugriffsrechten ausgeführt werden, z. B. Netzwerkdienste. In solchen Fällen gelten unter Umständen für einige Verzeichnisse im Dateisystem Sicherheitseinschränkungen, die den Zugriff durch den Zielprozess verhindern. Daher liegt es in der Verantwortung des Profilerentwicklers, die Profiler-DLL an einem Ort zu installieren, der entsprechenden Zugriff gestattet.
Fehler werden im Anwendungsereignisprotokoll protokolliert.
Schrittweises Anfügen
Der Triggerprozess ruft AttachProfiler auf und identifiziert den Zielprozess und den anzufügenden Profiler. Optional werden Daten übergeben, die für den Profiler vorgesehen sind, nachdem dieser angefügt worden ist.
Innerhalb des Zielprozesses empfängt die CLR die Anfügeanforderung.
Innerhalb des Zielprozesses erstellt die CLR das Profilerobjekt und ruft die ICorProfilerCallback3-Schnittstelle davon ab.
Die CLR ruft dann die InitializeForAttach-Methode auf und übergibt die Daten, die im Triggerprozess in die Anfügeanforderung eingeschlossen wurden.
Der Profiler führt eine Selbstinitialisierung durch, aktiviert die erforderlichen Rückrufe und führt eine erfolgreiche Rückgabe aus.
Innerhalb des Triggerprozesses gibt AttachProfiler ein S_OK HRESULT zurück, durch das angegeben wird, dass der Profiler angefügt wurde. Der Triggerprozess ist nicht mehr relevant.
Die Profilerstellung läuft ab diesem Punkt wie in allen früheren Versionen ab.
Suchen der AttachProfiler-Methode
Triggerprozesse können die AttachProfiler-Methode suchen, indem die folgenden Schritte ausgeführt werden:
Rufen Sie die LoadLibrary-Methode auf, um "mscoree.dll" zu laden und die CLRCreateInstance-Methode zu suchen.
Rufen Sie die CLRCreateInstance-Methode mit dem CLSID_CLRMetaHost-Argument und dem IID_ICLRMetaHost-Argument auf. Dabei wird eine ICLRMetaHost-Schnittstelle zurückgegeben.
Rufen Sie die ICLRMetaHost::EnumerateLoadedRuntimes-Methode auf, um im Prozess eine ICLRRuntimeInfo-Schnittstelle für jede CLR-Instanz abzurufen.
Durchlaufen Sie die ICLRRuntimeInfo-Schnittstellen, bis Sie auf die Schnittstelle stoßen, an die der Profiler angefügt werden soll.
Rufen Sie die ICLRRuntimeInfo::GetInterface-Methode mit einem Argument von IID_ICLRProfiling auf, um eine ICLRProfiling-Schnittstelle abzurufen, auf der die AttachProfiler-Methode bereitgestellt wird.
Anfügen an eine Zielanwendung vor dem Laden der Laufzeit
Diese Funktion wird nicht unterstützt. Wenn im Triggerprozess die AttachProfiler-Methode durch Angeben eines Prozesses aufgerufen wird, der zur Laufzeit noch nicht geladen wurde, gibt die AttachProfiler-Methode ein Fehler-HRESULT zurück. Wenn ein Benutzer ab dem Laden der Laufzeit ein Profil für eine Anwendung erstellen möchte, muss der Benutzer die entsprechenden Umgebungsvariablen festlegen (oder eine Anwendung ausführen, für die der Profilerentwickler eine entsprechende Anweisung erteilt hat), bevor die Zielanwendung gestartet wird.
Einschränkungen für Anfügevorgänge
Nur eine Teilmenge der ICorProfilerCallback-Methode und der ICorProfilerInfo-Methode steht für Anfügeprofiler zur Verfügung (siehe Erläuterungen in den folgenden Abschnitten).
ICorProfilerCallback-Einschränkungen
Wenn der Profiler die ICorProfilerInfo::SetEventMask-Methode aufruft, dürfen nur Ereignisflags angegeben werden, die in der COR_PRF_ALLOWABLE_AFTER_ATTACH-Enumeration angezeigt werden. Es sind keine anderen Rückrufe für Anfügeprofiler verfügbar. Wenn ein Anfügeprofiler versucht, ein Flag anzugeben, das nicht in der COR_PRF_ALLOWABLE_AFTER_ATTACH-Bitmaske enthalten ist, gibt ICorProfilerInfo::SetEventMask ein CORPROF_E_UNSUPPORTED_FOR_ATTACHING_PROFILER HRESULT zurück.
ICorProfilerInfo-Einschränkungen
Wenn ein Profiler, der durch Aufrufen der AttachProfiler-Methode geladen wurde, versucht, eine der folgenden eingeschränkten Methoden (ICorProfilerInfo oder ICorProfilerInfo2) aufzurufen, gibt diese Methode ein CORPROF_E_UNSUPPORTED_FOR_ATTACHING_PROFILER HRESULT zurück.
Eingeschränkte ICorProfilerInfo-Methoden:
Eingeschränkte ICorProfilerInfo2-Methoden:
Eingeschränkte ICorProfilerInfo3-Methoden:
Beispiel für das Anfügen eines Profilers
In diesem Beispiel wird angenommen, dass dem Triggerprozess bereits Folgendes bekannt ist: die Prozess-ID (PID) des Zielprozesses, die Anzahl der Millisekunden, die auf ein Timeout gewartet werden soll, die CLSID des zu ladenden Profilers sowie der vollständige Pfad zur DLL-Datei des Profilers. Dabei wird auch davon ausgegangen, dass der Profilerentwickler eine Struktur mit dem Namen MyProfilerConfigData, die Konfigurationsdaten für den Profiler enthält, und eine Funktion mit dem Namen PopulateMyProfilerConfigData definiert hat, die Konfigurationsdaten in diese Struktur einbindet.
HRESULT CallAttachProfiler(DWORD dwProfileeProcID, DWORD dwMillisecondsTimeout,
GUID profilerCLSID, LPCWSTR wszProfilerPath)
{
// This can be a data type of your own choosing for sending configuration data
// to your profiler:
MyProfilerConfigData profConfig;
PopulateMyProfilerConfigData(&profConfig);
LPVOID pvClientData = (LPVOID) &profConfig;
DWORD cbClientData = sizeof(profConfig);
ICLRMetaHost * pMetaHost = NULL;
IEnumUnknown * pEnum = NULL;
IUnknown * pUnk = NULL;
ICLRRuntimeInfo * pRuntime = NULL;
ICLRProfiling * pProfiling = NULL;
HRESULT hr = E_FAIL;
hModule = LoadLibrary(L"mscoree.dll");
if (hModule == NULL)
goto Cleanup;
CLRCreateInstanceFnPtr pfnCreateInterface =
(CLRCreateInstanceFnPtr)GetProcAddress(hModule, "CLRCreateInterface");
if (pfnCreateInterface == NULL)
goto Cleanup;
hr = (*pfnCreateInterface)(CLSID_CLRMetaHost, IID_ICLRMetaHost, (LPVOID *)&pMetaHost);
if (FAILED(hr))
goto Cleanup;
hr = pMetaHost->EnumerateLoadedRuntimes(hProcess, &pEnum);
if (FAILED(hr))
goto Cleanup;
while (pEnum->Next(1, &pUnk, NULL) == S_OK)
{
hr = pUnk->QueryInterface(IID_ICLRRuntimeInfo, (LPVOID *) &pRuntime);
if (FAILED(hr))
{
pUnk->Release();
pUnk = NULL;
continue;
}
WCHAR wszVersion[30];
DWORD cchVersion = sizeof(wszVersion)/sizeof(wszVersion[0]);
hr = pRuntime->GetVersionString(wszVersion, &cchVersion);
if (SUCCEEDED(hr) &&
(cchVersion >= 3) &&
((wszVersion[0] == L'v') || (wszVersion[0] == L'V')) &&
((wszVersion[1] >= L'4') || (wszVersion[2] != L'.')))
{
hr = pRuntime->GetInterface(CLSID_CLRProfiling, IID_ICLRProfiling, (LPVOID *)&pProfiling);
if (SUCCEEDED(hr))
{
hr = pProfiling->AttachProfiler(
dwProfileeProcID,
dwMillisecondsTimeout,
profilerCLSID,
wszProfilerPath,
pvClientData,
cbClientData
);
pProfiling->Release();
pProfiling = NULL;
break;
}
}
pRuntime->Release();
pRuntime = NULL;
pUnk->Release();
pUnk = NULL;
}
Cleanup:
if (pProfiling != NULL)
{
pProfiling->Release();
pProfiling = NULL;
}
if (pRuntime != NULL)
{
pRuntime->Release();
pRuntime = NULL;
}
if (pUnk != NULL)
{
pUnk->Release();
pUnk = NULL;
}
if (pEnum != NULL)
{
pEnum->Release();
pEnum = NULL;
}
if (pMetaHost != NULL)
{
pMetaHost->Release();
pMetaHost = NULL;
}
if (hModule != NULL)
{
FreeLibrary(hModule);
hModule = NULL;
}
return hr;
}
Initialisieren des Profilers nach dem Anfügen
Ab .NET Framework 4 wird die ICorProfilerCallback3::InitializeForAttach-Methode als Anfügeäquivalent der ICorProfilerCallback::Initialize-Methode angegeben. Die CLR ruft InitializeForAttach auf, um dem Profiler eine Gelegenheit zur Initialisierung des Zustands nach dem Anfügevorgang zu geben. In diesem Rückruf ruft der Profiler die ICorProfilerInfo::SetEventMask-Methode auf, um mindestens ein Ereignis anzufordern. Im Gegensatz zu während der ICorProfilerCallback::Initialize-Methode ausgeführten Aufrufen können bei Aufrufen der SetEventMask-Methode, die von InitializeForAttach ausgeführt werden, nur Bits festgelegt werden, die in der COR_PRF_ALLOWABLE_AFTER_ATTACH-Bitmaske enthalten sind (siehe Einschränkungen für Anfügevorgänge).
Bei Empfang eines Fehlercodes aus InitializeForAttach protokolliert die CLR den Fehler im Windows-Anwendungsereignisprotokoll, gibt die Profilerrückrufschnittstelle frei und entlädt den Profiler. AttachProfiler gibt den gleichen Fehlercode wie InitializeForAttach zurück.
Abschließen des Anfügens eines Profilers
Ab .NET Framework 4 wird der ICorProfilerCallback3::ProfilerAttachComplete-Rückruf nach der InitializeForAttach-Methode ausgegeben. ProfilerAttachComplete gibt an, dass die Rückrufe, die vom Profiler in der InitializeForAttach-Methode angefordert wurden, aktiviert wurden, und dass der Profiler nun einen Ausgleich für die zugeordneten IDs ausführen kann, ohne fehlenden Benachrichtigungen Beachtung schenken zu müssen.
Beispiel: Angenommen, der Profiler hat COR_PRF_MONITOR_MODULE_LOADS während seines InitializeForAttach-Rückrufs festgelegt. Wenn der Profiler aus InitializeForAttach zurückkehrt, aktiviert die CLR ModuleLoad-Rückrufe und gibt dann den ProfilerAttachComplete-Rückruf aus. Der Profiler kann anschließend mithilfe der ICorProfilerInfo3::EnumModules-Methode während des ProfilerAttachComplete-Rückrufs ModuleIDs für alle derzeit geladenen Module auflisten. Außerdem werden ModuleLoad-Ereignisse für alle neuen Module ausgegeben, die während der Enumeration geladen werden. Profiler müssen alle vorkommenden Duplikate ordnungsgemäß bearbeiten. Beispiel: Ein Modul, das nur zum richtigen Zeitpunkt geladen wird, während ein Profiler angefügt wird, kann zweimal in der ModuleID erscheinen: in der Enumeration, die von ICorProfilerInfo3::EnumModules zurückgegeben wird, und in einem ModuleLoadFinished-Rückruf.
Trennen eines Profilers
Die Anfügeanforderung muss vom Triggerprozess prozessextern initiiert werden. Allerdings wird die Trennungsanforderung von der Profiler-DLL prozessintern initiiert, wenn die ICorProfilerInfo3::RequestProfilerDetach-Methode aufgerufen wird. Wenn der Profilerentwickler die Trennungsanforderung prozessextern initiieren möchte (z. B. von einer benutzerdefinierten Benutzeroberfläche), muss der Entwickler einen prozessübergreifenden Kommunikationsmechanismus erstellen, um der Profiler-DLL (die parallel zur Zielanwendung ausgeführt wird) zu signalisieren, dass RequestProfilerDetach aufgerufen werden soll.
RequestProfilerDetach führt vor der Rückgabe einen Teil der Arbeit synchron aus (dazu gehört auch das Festlegen des internen Zustands, um das Senden von Ereignissen an die Profiler-DLL zu verhindern). Der Rest der Arbeit wird asynchron nach Rückgabe von RequestProfilerDetach ausgeführt. Diese verbleibende Arbeit wird in einem separaten Thread (DetachThread) ausgeführt. Dazu zählen Abrufvorgänge und die Überprüfung, ob der gesamte Profilercode von den Stapeln aller Anwendungsthreads geholt wurde. Wenn RequestProfilerDetach fertig gestellt wurde, empfängt der Profiler einen abschließenden Rückruf (ICorProfilerCallback3::ProfilerDetachSucceeded), bevor die CLR die Schnittstelle und Codeheaps des Profilers freigibt und die DLL des Profilers entladen wird.
Threadüberlegungen während des Trennens
Die Ausführungssteuerung kann auf viele verschiedene Arten an einen Profiler übertragen werden. Allerdings darf das Steuerelement nach dem Entladen nicht an einen Profiler übergeben werden, und die Laufzeit muss die Zuständigkeit freigeben, um sicherzustellen, dass dieses Verhalten nicht auftritt:
Die Laufzeit besitzt keine Informationen zu Threads, die vom Profiler erstellt oder übernommen werden und die Profilercode für den Stapel enthalten oder demnächst ggf. enthalten werden. Daher muss der Profiler sicherstellen, dass alle Threads beendet werden, die von ihm erstellt wurden und es müssen alle Sampling- oder Übernahmevorgänge beendet werden, bevor ICorProfilerInfo3::RequestProfilerDetach aufgerufen wird. Es gibt eine Ausnahme von dieser Regel: Der Profiler verwendet möglicherweise einen Thread, der für den Aufruf von ICorProfilerInfo3::RequestProfilerDetach erstellt wurde. Allerdings muss für diesen Thread dessen eigener Verweis auf die Profiler-DLL über die LoadLibrary-Funktion und die FreeLibraryAndExitThread-Funktion beibehalten werden (Details finden Sie im nächsten Abschnitt).
Nachdem der Profiler RequestProfilerDetach aufgerufen hat, muss die Laufzeit sicherstellen, dass ICorProfilerCallback-Methoden nicht bewirken, dass Profilercode auf dem Stapel beliebiger Threads verbleibt, wenn die Laufzeit versucht, den Profiler vollständig zu entladen.
Schrittweises Trennen
Die folgenden Schritte finden statt, wenn ein Profiler getrennt wird:
Der Profiler beendet alle Threads, die von ihm erstellt wurden, und beendet sämtliche Sampling- und Übernahmevorgänge, bevor ICorProfilerInfo3::RequestProfilerDetach aufgerufen wird. Dabei gilt die folgende Ausnahme:
Ein Profiler implementiert möglicherweise die Trennung mithilfe eines seiner eigenen Threads, um die ICorProfilerInfo3::RequestProfilerDetach-Methode aufzurufen (anstatt einen von der CLR erstellten Thread zu verwenden). Wenn ein Profiler dieses Verhalten implementiert, darf der Profilerthread vorhanden sein, wenn die RequestProfilerDetach-Methode aufgerufen wird (da dies der Thread ist, der die Methode aufruft). Wenn ein Profiler jedoch diese Implementierung wählt, muss Folgendes sichergestellt werden:
Für den Thread, der für den Aufruf von RequestProfilerDetach verbleibt, muss dessen eigener Verweis auf die Profiler-DLL beibehalten werden (durch Aufrufen der LoadLibrary-Funktion mit sich selbst).
Nachdem der Thread RequestProfilerDetach aufgerufen hat, muss sofort die FreeLibraryAndExitThread-Funktion aufgerufen werden, um die Sperre der Profiler-DLL aufzuheben und den Vorgang zu beenden.
RequestProfilerDetach legt den internen Zustand der Laufzeit fest, um den Profiler als deaktiviert einzustufen. Dies verhindert künftige Aufrufe in den Profiler durch die Rückrufmethoden.
RequestProfilerDetach signalisiert DetachThread, mit der Überprüfung zu beginnen, ob alle Threads sämtliche verbleibenden Rückrufmethoden von ihren Stapeln geholt haben.
RequestProfilerDetach wird mit einem Statuscode zurückgegeben, der angibt, ob der Trennvorgang gestartet wurde.
An dieser Stelle lässt die CLR keine weiteren Aufrufe vom Profiler in die CLR durch die folgenden Schnittstellenmethoden mehr zu: ICorProfilerInfo, ICorProfilerInfo2 und ICorProfilerInfo3. Alle derartigen Aufrufe schlagen sofort fehl und geben ein CORPROF_E_PROFILER_DETACHING HRESULT zurück.
Der Profiler kehrt zurück, oder der Thread wird beendet. Wenn der Profiler mit einem seiner eigenen Threads RequestProfilerDetach aufgerufen hat, muss FreeLibraryAndExitThread nun von diesem Thread aufgerufen werden. Wenn der Profiler RequestProfilerDetach mit einem CLR-Thread aufgerufen hat (d. h. innerhalb eines Rückrufs), übergibt der Profiler die Steuerung wieder an die CLR.
DetachThread überprüft inzwischen weiterhin, ob alle Threads sämtliche verbleibenden Rückrufmethoden von ihren Stapeln geholt haben.
Wenn DetachThread festgestellt hat, dass keine Rückrufe auf dem Stapel eines Threads verbleiben, wird die ICorProfilerCallback3::ProfilerDetachSucceeded-Methode aufgerufen. Der Profiler sollte die minimale Arbeit in der ProfilerDetachSucceeded-Methode ausführen und schnell zurückkehren.
DetachThread führt einen abschließenden Release()-Vorgang auf der ICorProfilerCallback-Schnittstelle des Profilers aus.
DetachThread ruft die FreeLibrary-Funktion der Profiler-DLL auf.
Einschränkungen für Trennvorgänge
Trennvorgänge werden in den folgenden Szenarien nicht unterstützt:
Wenn der Profiler unveränderliche Ereignisflags festlegt.
Wenn der Profiler Enter/Leave/Tailcall (ELT)-Überprüfungen verwendet.
Wenn der Profiler Microsoft Intermediate Language (MSIL) Instrumentation verwendet (z. B. durch Aufrufen der SetILFunctionBody-Methode).
Wenn in diesen Szenarien eine Profilertrennung vorgenommen wird, gibt RequestProfilerDetach ein fehlerhaftes HRESULT zurück.
Debuggen
Die Profileranfüge- und -trennfunktion verhindert nicht das Debuggen einer Anwendung. In einer Anwendung sind zu jedem beliebigen Zeitpunkt während der Debugsitzung Profileranfüge- und -trennvorgänge möglich. Umgekehrt ist in einer Anwendung mit einer Profileranfügung (und späterer Trennung) auch zu jedem beliebigen Zeitpunkt ein Debuggeranfüge- und -trennvorgang möglich. Allerdings kann für eine Anwendung, die von einem Debugger angehalten wird, kein Profil erstellt werden, da sie nicht auf eine Anforderung einer Profileranfügung reagieren kann.
Siehe auch
Konzepte
Übersicht über die Profilerstellung