Attachement au profileur et détachement du profileur
Dans les versions du .NET Framework antérieures au .NET Framework version 4, une application et son profileur devaient être chargés en même temps. Lorsqu'une application démarrait, le runtime déterminait si elle devait être profilée en examinant des variables d'environnement et des paramètres du Registre, puis chargeait le profileur requis. Ce profileur restait ensuite dans l'espace de processus jusqu'à ce que l'application soit arrêtée.
À partir du .NET Framework 4, vous pouvez attacher un profileur à une application après le démarrage de celle-ci, profiler l'application, puis détacher le profileur avant l'arrêt de l'application. Une fois le profileur détaché, l'application continue à fonctionner comme elle le faisait avant l'attachement du profileur. En d'autres termes, il ne reste aucune trace de la présence temporaire du profileur dans l'espace de processus.
De plus, à compter du .NET Framework 4, les méthodes d'attachement et de détachement de profilage et les améliorations apportées aux API de profilage liées vous permettent d'utiliser les outils basés sur le profileur comme des outils de diagnostic prédéfinis juste-à-temps.
Ces améliorations sont traitées dans les sections suivantes :
Attachement d'un profileur
Détails de l'attachement
Attachement étape par étape
Localisation de la méthode AttachProfiler
Attachement à une application cible avant le chargement du runtime
Restrictions des attachements
Exemple d'attachement de profileur
Initialisation du profileur après attachement
Finalisation de l'attachement du profileur
Détachement d'un profileur
Considérations sur les threads pendant le détachement
Détachement étape par étape
Restrictions des détachements
Débogage
Attachement d'un profileur
Deux processus distincts sont requis pour attacher un profileur à une application en cours d'exécution : processus déclencheur et processus cible.
Le processus déclencheur est un exécutable qui entraîne le chargement de la DLL du profileur dans l'espace de processus d'une application en cours d'exécution. Le processus déclencheur utilise la méthode ICLRProfiling::AttachProfiler pour demander au Common Language Runtime (CLR) de charger la DLL du profileur. Une fois la DLL du profileur chargée dans le processus cible, le processus déclencheur peut se poursuivre ou s'arrêter, au choix du développeur du profileur.
Le processus cible contient l'application que vous souhaitez profiler et le CLR. Une fois la méthode AttachProfiler appelée, la DLL du profileur est chargée dans cet espace de processus avec l'application cible.
Détails de l'attachement
AttachProfiler attache le profileur spécifié au processus spécifié, et peut passer des données au profileur. Il attend la fin du délai d'attente spécifié pour l'attachement.
Dans le processus cible, la procédure de chargement d'un profileur au moment de l'attachement est semblable à la procédure de chargement d'un profileur au moment du démarrage : le CLR CoCreates le CLSID donné, recherche l'interface ICorProfilerCallback3 et appelle la méthode ICorProfilerCallback3::InitializeForAttach. Si ces opérations réussissent dans le délai d'attente alloué, AttachProfiler retourne un S_OK HRESULT. Par conséquent, le processus déclencheur doit choisir un délai d'attente suffisant pour l'initialisation du profileur.
Remarque |
---|
Les délais d'attente peuvent se produire si un finaliseur du processus cible s'est exécuté pendant une durée supérieure à la valeur du délai d'attente, ce qui génère un HRESULT_FROM_WIN32 (ERROR_TIMEOUT).Si vous recevez cette erreur, vous pouvez réessayer l'opération d'attachement. |
Si le délai d'attente arrive à expiration avant la fin de l'attachement, AttachProfiler retourne une erreur HRESULT. Il est cependant possible que l'attachement ait réussi. Dans ce cas, il existe d'autres façons de déterminer la réussite. Les développeurs de profileurs disposent souvent un canal de communication personnalisé entre leur processus déclencheur et l'application cible. Ce canal de communication peut être utilisé pour détecter la réussite d'un attachement. Le processus déclencheur peut également détecter la réussite en appelant à nouveau AttachProfiler et en recevant CORPROF_E_PROFILER_ALREADY_ACTIVE.
De plus, le processus cible doit disposer de droits d'accès suffisants pour être en mesure de charger la DLL du profileur. Ceci peut poser problème lors de l'attachement à des services (par exemple, le processus W3wp.exe) qui peuvent s'exécuter sous des comptes avec des droits d'accès restreints, tels que SERVICE RÉSEAU. Dans ce cas, certains répertoires du système de fichiers peuvent posséder des restrictions de sécurité qui empêchent l'accès par le processus cible. Par conséquent, il revient au développeur du profileur d'installer la DLL du profileur dans un emplacement qui autorise un accès approprié.
Les erreurs sont enregistrées dans le journal des événements de l'application.
Attachement étape par étape
Le processus déclencheur appelle AttachProfiler, en identifiant le processus cible et le profileur à joindre, et en passant éventuellement des données à transmettre au profileur après l'attachement.
Au sein du processus cible, le CLR reçoit la demande d'attachement.
Dans le processus cible, le CLR crée l'objet de profileur et en obtient l'interface ICorProfilerCallback3.
Le CLR appelle ensuite la méthode InitializeForAttach, en passant les données que le processus déclencheur a inclus dans la demande d'attachement.
Le profileur s'initialise, active les rappels dont il a besoin et retourne avec succès.
Dans le processus déclencheur, AttachProfiler retourne un S_OK HRESULT, qui indique que le profileur a été attaché avec succès. Le processus déclencheur cesse d'être pertinent.
Le profilage continue à partir de ce point comme dans toutes les versions antérieures.
Localisation de la méthode AttachProfiler
Les processus déclencheurs peuvent localiser la méthode AttachProfiler en suivant ces étapes :
Appel de la méthode LoadLibrary pour charger mscoree.dll et trouver la méthode CLRCreateInstance.
Appel de la méthode CLRCreateInstance avec les arguments CLSID_CLRMetaHost et IID_ICLRMetaHost, qui retourne une interface ICLRMetaHost.
Appel de la méthode ICLRMetaHost::EnumerateLoadedRuntimes pour extraire une interface ICLRRuntimeInfo pour chaque instance du CLR dans le processus.
Itération au sein des interfaces ICLRRuntimeInfo jusqu'à ce que l'interface souhaitée à laquelle attacher le profileur soit trouvée.
Appel de la méthode ICLRRuntimeInfo::GetInterface avec l'argument IID_ICLRProfiling pour obtenir une interface ICLRProfiling, qui fournit la méthode AttachProfiler.
Attachement à une application cible avant le chargement du runtime
Cette fonctionnalité n'est pas prise en charge. Si le processus déclencheur essaie d'appeler la méthode AttachProfiler en spécifiant un processus qui n'a pas encore chargé le runtime, la méthode AttachProfiler retourne un HRESULT d'échec. Si un utilisateur souhaite profiler une application à partir du moment où le runtime est chargé, il doit définir les variables d'environnement appropriées (ou demander à une application créée par le développeur de profileur de le faire) avant de lancer l'application cible.
Restrictions des attachements
Seul un sous-ensemble des méthodes ICorProfilerCallback et ICorProfilerInfo sont disponibles pour attacher des profileurs, comme indiqué dans les sections suivantes.
Restrictions ICorProfilerCallback
Lorsque le profileur appelle la méthode ICorProfilerInfo::SetEventMask, il doit uniquement spécifier les indicateurs d'événement qui s'affichent dans l'énumération COR_PRF_ALLOWABLE_AFTER_ATTACH. Aucun autre rappel n'est disponible pour attacher les profileurs. Si un profileur en cours d'attachement essaie de spécifier un indicateur qui ne figure pas dans le masque de bits COR_PRF_ALLOWABLE_AFTER_ATTACH, ICorProfilerInfo::SetEventMask retourne un CORPROF_E_UNSUPPORTED_FOR_ATTACHING_PROFILER HRESULT.
Restrictions ICorProfilerInfo
Si un profileur chargé en appelant la méthode AttachProfiler essaie d'appeler l'une des méthodes ICorProfilerInfo ou ICorProfilerInfo2 restreintes suivantes, cette méthode retourne un CORPROF_E_UNSUPPORTED_FOR_ATTACHING_PROFILER HRESULT.
Méthodes ICorProfilerInfo restreintes :
Méthodes ICorProfilerInfo2 restreintes :
Méthodes ICorProfilerInfo3 restreintes :
Exemple d'attachement de profileur
Cet exemple suppose que le processus déclencheur connaît déjà l'identificateur de processus (PID) du processus cible, le nombre de millisecondes du délai d'attente, le CLSID du profileur à charger et le chemin d'accès complet au fichier DLL du profileur. Il suppose également que le développeur de profileur a défini une structure appelée MyProfilerConfigData, qui conserve les données de configuration du profileur, et une fonction appelée PopulateMyProfilerConfigData, qui place les données de configuration dans cette structure.
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;
}
Initialisation du profileur après attachement
À partir du .NET Framework 4, la méthode ICorProfilerCallback3::InitializeForAttach est fournie en tant qu'équivalent en matière d'attachement de la méthode ICorProfilerCallback::Initialize. Le CLR appelle InitializeForAttach pour permettre au profileur d'initialiser son état après une opération d'attachement. Dans ce rappel, le profileur appelle la méthode ICorProfilerInfo::SetEventMask pour demander un ou plusieurs événements. Contrairement aux appels effectués pendant la méthode ICorProfilerCallback::Initialize, les appels à la méthode SetEventMask effectués à partir de InitializeForAttach peuvent uniquement définir des bits qui figurent dans le masque de bits COR_PRF_ALLOWABLE_AFTER_ATTACH (consultez Restrictions des attachements).
Après réception d'un code d'erreur à partir de InitializeForAttach, le CLR enregistre l'échec dans le journal des événements des applications Windows, libère l'interface de rappel du profileur et décharge le profileur. AttachProfiler retourne le même code d'erreur que InitializeForAttach.
Finalisation de l'attachement du profileur
À partir du .NET Framework 4, le rappel ICorProfilerCallback3::ProfilerAttachComplete est émis après la méthode InitializeForAttach. ProfilerAttachComplete indique que les rappels demandés par le profileur dans la méthode InitializeForAttach ont été activés, et que le profileur peut maintenant exécuter un rattrapage sur les ID associés sans risquer de manquer des notifications.
Par exemple, supposez que le profileur a défini COR_PRF_MONITOR_MODULE_LOADS pendant son rappel InitializeForAttach. Lorsque le profileur retourne de InitializeForAttach, le CLR autorise les rappels ModuleLoad, puis émet le rappel ProfilerAttachComplete. Le profileur peut ensuite utiliser la méthode ICorProfilerInfo3::EnumModules pour énumérer les ModuleID de tous les modules actuellement chargés pendant son rappel ProfilerAttachComplete. De plus, les événements ModuleLoad sont émis pour tous les nouveaux modules chargés pendant l'énumération. Les profileurs doivent gérer correctement tous les doublons qu'ils rencontrent. Par exemple, un module qui se charge juste au moment où un profileur est en cours d'attachement peut entraîner une double apparition de ModuleID : dans l'énumération retournée par ICorProfilerInfo3::EnumModules et dans un rappel ModuleLoadFinished.
Détachement d'un profileur
La demande d'attachement doit être initialisée hors processus par le processus déclencheur. Toutefois, la demande de détachement est initialisée en cours de processus par la DLL du profileur lorsqu'il appelle la méthode ICorProfilerInfo3::RequestProfilerDetach. Si le développeur de profileur souhaite initialiser la demande de détachement hors processus (par exemple, à partir d'une interface utilisateur personnalisée), il doit créer un mécanisme de communication interprocessus pour signaler la DLL du profileur (exécutée en parallèle de l'application cible) pour appeler RequestProfilerDetach.
RequestProfilerDetach exécute une partie de son travail (notamment la définition de son état interne pour empêcher l'envoi d'événements à la DLL du profileur) de façon synchrone avant de retourner. Le reste de son travail est réalisé de façon asynchrone, après que RequestProfilerDetach a été retourné. Ce travail restant s'exécute sur un thread séparé (DetachThread) et inclut l'interrogation et la garantie que tout le code du profileur a été dépilé des piles de tous les threads d'application. Lorsque RequestProfilerDetach est terminé, le profileur reçoit un dernier rappel (ICorProfilerCallback3::ProfilerDetachSucceeded), avant que le CLR ne libère les tas de code et d'interface du profileur et ne décharge la DLL du profileur.
Considérations sur les threads pendant le détachement
Le contrôle de l'exécution peut être transféré à un profileur de nombreuses façons. Cependant, le contrôle ne doit pas être passé à un profileur après qu'il a été déchargé, et le profileur et le runtime doivent tous deux partager la responsabilité de la vérification que ce problème ne se produit pas :
Le runtime ne sait rien à propos des threads qui sont créés ou détournés par le profileur qui contient, ou va peut-être bientôt contenir, le code de profileur sur la pile. Par conséquent, le profileur doit vérifier qu'il quitte bien tous les threads il a créés, et doit arrêter tout échantillonnage ou détournement avant d'appeler ICorProfilerInfo3::RequestProfilerDetach. Il existe une exception à cette règle : le profileur peut utiliser un thread qu'il a créé pour appeler ICorProfilerInfo3::RequestProfilerDetach. Toutefois, ce thread doit conserver sa propre référence à la DLL du profileur via les fonctions LoadLibrary et FreeLibraryAndExitThread (consultez la section suivante pour plus de détails).
Une fois que le profileur a appelé RequestProfilerDetach, le runtime doit s'assurer que les méthodes ICorProfilerCallback n'entraînent pas le maintien du code du profileur dans la pile des threads lorsque le runtime essaie de décharger entièrement le profileur.
Détachement étape par étape
Les étapes suivantes ont lieu lorsqu'un profileur est détaché :
Le profileur quitte tous les threads qu'il a créés, et arrête tout échantillonnage et détournement avant d'appeler ICorProfilerInfo3::RequestProfilerDetach, avec l'exception suivante :
Un profileur peut implémenter le détachement en utilisant l'un de ses propres threads pour appeler la méthode ICorProfilerInfo3::RequestProfilerDetach (au lieu d'utiliser un thread créé par le CLR). Si un profileur implémente ce comportement, le thread de ce profileur peut exister lorsque la méthode RequestProfilerDetach est appelée (car il s'agit du thread qui appelle la méthode). Toutefois, si un profileur choisit cette implémentation, il doit s'assurer des éléments suivants :
Le thread qui restera pour appeler RequestProfilerDetach doit garder sa propre référence à la DLL du profileur (en appelant la fonction LoadLibrary sur lui-même).
Une fois que le thread a appelé RequestProfilerDetach, il doit immédiatement appeler la fonction FreeLibraryAndExitThread pour libérer son maintien sur la DLL du profileur et quitter.
RequestProfilerDetach définit l'état interne du runtime de façon à considérer que le profileur est désactivé. Cela permet d'empêcher de futurs appels dans le profileur via les méthodes de rappel.
RequestProfilerDetach signale DetachThread pour commencer à vérifier que tous les threads ont dépilé les méthodes de rappel restantes de leurs piles.
RequestProfilerDetach retourne avec un code d'état qui indique si le détachement a été démarré avec succès.
À ce stade, le CLR rejette tous les autres appels du profileur dans le CLR via les méthodes d'interface ICorProfilerInfo, ICorProfilerInfo2 et ICorProfilerInfo3. De tels appels échouent immédiatement et retournent un CORPROF_E_PROFILER_DETACHING HRESULT.
Le profileur retourne ou quitte le thread. Si le profileur a utilisé l'un de ses propres threads pour effectuer cet appel à RequestProfilerDetach, il doit appeler FreeLibraryAndExitThread à partir de ce thread maintenant. Si le profileur a appelé RequestProfilerDetach à l'aide d'un thread du CLR (à savoir, à partir d'un rappel), le profileur rend le contrôle au CLR.
Pendant ce temps, DetachThread continue à vérifier si tous les threads ont dépilé les méthodes de rappel restantes de leurs piles.
Lorsque DetachThread détermine qu'aucun rappel ne reste sur la pile des threads, il appelle la méthode ICorProfilerCallback3::ProfilerDetachSucceeded. Le profileur doit effectuer un travail minime dans la méthode ProfilerDetachSucceeded et retourner rapidement.
DetachThread exécute le dernier Release() sur l'interface ICorProfilerCallback du profileur.
DetachThread appelle la fonction FreeLibrary sur la DLL du profileur.
Restrictions des détachements
Le détachement n'est pas pris en charge dans les scénarios suivants :
Lorsque le profileur définit des indicateurs d'événement immuables.
Lorsque le profileur utilise des sondes enter/leave/tailcall (ELT).
Lorsque le profileur utilise l'instrumentation MILS (Microsoft Intermediate Language) (par exemple, en appelant la méthode SetILFunctionBody).
Si le détachement du profileur est tenté dans ces scénarios, RequestProfilerDetach retourne une erreur HRESULT.
Débogage
Les fonctionnalités d'attachement et de détachement du profileur n'empêchent pas une application d'être déboguée. Une application peut se voir attacher, ou détacher, un profileur, à tout moment d'une session de débogage. Inversement, une application à laquelle est attaché un profileur (qui est ultérieurement détaché) peut également se voir attacher, ou détacher, un débogueur à tout moment. Toutefois, une application suspendue par un débogueur ne peut pas être profilée car elle ne peut pas répondre à une demande d'attachement de profileur.