Connessione e disconnessione del profiler

Nelle versioni di .NET Framework precedenti a .NET Framework 4, un'applicazione e il relativo profiler devono essere caricati contemporaneamente. Quando un'applicazione viene avviata, il runtime determina se l'applicazione deve essere profilata esaminando le variabili di ambiente e le impostazioni del Registro di sistema, quindi carica il profiler necessario. Il profiler rimane quindi nello spazio del processo fino a che l'applicazione non viene chiusa.

A partire da .NET Framework 4, è possibile connettere un profiler a un'applicazione dopo che l'applicazione è stata avviata, profilare l'applicazione, quindi disconnettere il profiler prima della chiusura dell'applicazione. Dopo la disconnessione del profiler, l'applicazione continua a essere in esecuzione come prima della connessione. In altre parole, non rimane traccia della presenza temporanea del profiler nello spazio del processo.

Inoltre, a partire da .NET Framework 4, i metodi di connessione e disconnessione del profiler e i miglioramenti dell'API di profilatura correlati consentono di usare gli strumenti basati sul profiler come strumenti diagnostici JIT (Just-In-Time) predefiniti.

Tali miglioramenti sono descritti nelle seguenti sezioni:

  • Connessione di un profiler

    • Dettagli della connessione

    • Procedura dettagliata per la connessione

    • Individuazione del metodo AttachProfiler

    • Connessione a un'applicazione di destinazione prima del caricamento del runtime

    • Restrizioni della connessione

    • Esempio di connessione del profiler

    • Inizializzazione del profiler dopo la connessione

    • Completamento della connessione del profiler

  • Disconnessione di un profiler

    • Considerazioni sui thread durante la disconnessione

    • Procedura dettagliata per la disconnessione

    • Restrizioni della disconnessione

  • Debug

Connessione di un profiler

Per connettere un profiler a un'applicazione in esecuzione, sono necessari due processi distinti: il processo trigger e il processo di destinazione.

  • Il processo trigger è un file eseguibile che provoca il caricamento della DLL del profiler nello spazio del processo di un'applicazione in esecuzione. Il processo trigger usa il metodo ICLRProfiling::AttachProfiler per richiedere a Common Language Runtime (CLR) il caricamento della DLL del profiler. Quando la DLL del profiler risulta caricata nel processo di destinazione, il processo trigger può rimanere attivo o essere chiuso, a discrezione dello sviluppatore del profiler.

  • Il processo di destinazione contiene l'applicazione che si desidera profilare e CLR. Dopo la chiamata al metodo AttachProfiler, la DLL del profiler viene caricata in questo spazio del processo insieme all'applicazione di destinazione.

Dd997942.collapse_all(it-it,VS.110).gifDettagli della connessione

AttachProfiler connette il profiler specificato al processo desiderato, passando facoltativamente alcuni dati al profiler. Attende il timeout specificato per il completamento della connessione.

Nel processo di destinazione, la procedura per il caricamento di un profiler in fase di connessione è simile a quella per il caricamento di un profiler in fase di avvio. CLR esegue l'operazione CoCreates per il CLSID specificato, esegue una query per trovare l'interfaccia ICorProfilerCallback3 e chiama il metodo ICorProfilerCallback3::InitializeForAttach. Se queste operazioni riescono entro il timeout specificato, AttachProfiler restituisce un HRESULT S_OK. Il processo trigger deve pertanto scegliere un timeout sufficiente a garantire il completamento dell'inizializzazione del profiler.

Nota

Il timeout può verificarsi perché un finalizzatore nel processo di destinazione è rimasto in esecuzione oltre il valore di timeout e ha restituito HRESULT_FROM_WIN32 (ERROR_TIMEOUT).Se si riceve questo errore, è possibile riprovare l'operazione di connessione.

Se il timeout scade prima del completamento della connessione, AttachProfiler restituisce un errore HRESULT. Tuttavia è possibile che la connessione sia riuscita. In tali casi, esistono alternative per stabilire se l'operazione è stata eseguita correttamente. Gli sviluppatori di profiler spesso hanno un canale di comunicazione personalizzato tra il processo trigger e l'applicazione di destinazione. Tale canale di comunicazione può essere usato per rilevare una connessione riuscita. Il processo trigger può rilevare la riuscita della connessione anche chiamando nuovamente AttachProfiler e ricevendo CORPROF_E_PROFILER_ALREADY_ACTIVE.

Inoltre, il processo di destinazione deve disporre di diritti di accesso sufficienti per caricare la DLL del profiler. Questo requisito può creare problemi quando si esegue la connessione a servizi, ad esempio il processo W3wp.exe, che possono essere in esecuzione tramite account con diritti di accesso limitati, ad esempio NETWORK SERVICE. In tali casi, è possibile che per alcune directory del file system siano impostate restrizioni di sicurezza che impediscono l'accesso da parte del processo di destinazione. Di conseguenza, è responsabilità dello sviluppatore del profiler installare la DLL del profiler in un percorso che consenta l'accesso appropriato.

Gli errori vengono registrati nel log eventi dell'applicazione.

Dd997942.collapse_all(it-it,VS.110).gifProcedura dettagliata per la connessione

  1. Il processo trigger chiama AttachProfiler, identificando il processo di destinazione e il profiler da connettere e passando facoltativamente i dati per il profiler dopo la connessione.

  2. Nel processo di destinazione, CLR riceve la richiesta di connessione.

  3. Nel processo di destinazione, CLR crea l'oggetto del profiler e ottiene l'interfaccia ICorProfilerCallback3.

  4. CLR chiama quindi il metodo InitializeForAttach, passando i dati che il processo trigger ha incluso nella richiesta di connessione.

  5. Il profiler si inizializza, abilita i callback necessari e termina la propria esecuzione.

  6. Nel processo trigger, AttachProfiler restituisce un HRESULT S_OK, per indicare che il profiler ha eseguito correttamente la connessione. Il processo trigger a questo punto cessa di essere rilevante.

  7. La profilatura continua da questo punto in avanti come in tutte le versioni precedenti.

Dd997942.collapse_all(it-it,VS.110).gifIndividuazione del metodo AttachProfiler

I processi trigger possono individuare il metodo AttachProfiler con i passaggi seguenti:

  1. Chiamare il metodo LoadLibrary per caricare mscoree.dll e trovare il metodo CLRCreateInstance.

  2. Chiamare il metodo CLRCreateInstance con gli argomenti CLSID_CLRMetaHost e IID_ICLRMetaHost, che restituisce un'interfaccia ICLRMetaHost.

  3. Chiamare il metodo ICLRMetaHost::EnumerateLoadedRuntimes per recuperare un'interfaccia ICLRRuntimeInfo per ogni istanza CLR del processo.

  4. Scorrere le interfacce ICLRRuntimeInfo fino a trovare l'interfaccia desiderata alla quale connettere il profiler.

  5. Chiamare il metodo ICLRRuntimeInfo::GetInterface con un argomento di IID_ICLRProfiling per ottenere un'interfaccia ICLRProfiling che fornisce il metodo AttachProfiler.

Dd997942.collapse_all(it-it,VS.110).gifConnessione a un'applicazione di destinazione prima del caricamento del runtime

Questa funzione non è più supportata. Se il processo trigger tenta di chiamare il metodo AttachProfiler specificando un processo che non ha ancora caricato il runtime, il metodo AttachProfiler restituisce un HRESULT di errore. Se un utente desidera profilare un'applicazione dal momento in cui viene caricato il runtime, deve impostare le variabili di ambiente appropriate o eseguire un'applicazione creata a tale scopo dallo sviluppatore del profiler prima di avviare l'applicazione di destinazione.

Dd997942.collapse_all(it-it,VS.110).gifRestrizioni della connessione

Per connettere i profiler, è possibile usare solo un sottoinsieme dei metodi ICorProfilerCallback e ICorProfilerInfo, come spiegato nelle sezioni seguenti.

Dd997942.collapse_all(it-it,VS.110).gifRestrizioni di ICorProfilerCallback

Quando il profiler chiama il metodo ICorProfilerInfo::SetEventMask, deve specificare solo i flag dell'evento presenti nella maschera di bit COR_PRF_ALLOWABLE_AFTER_ATTACH dell'enumerazione COR_PRF_MONITOR. Non sono disponibili altri callback per connettere i profiler. Se un profiler in fase di connessione tenta di specificare un flag non presente nella maschera di bit COR_PRF_ALLOWABLE_AFTER_ATTACH, ICorProfilerInfo::SetEventMask restituisce un HRESULT CORPROF_E_UNSUPPORTED_FOR_ATTACHING_PROFILER.

Dd997942.collapse_all(it-it,VS.110).gifRestrizioni di ICorProfilerInfo

Se un profiler caricato chiamando il metodo AttachProfiler tenta di chiamare uno dei metodi con restrizioni di ICorProfilerInfo o ICorProfilerInfo2 indicati di seguito, tale metodo restituirà un HRESULT CORPROF_E_UNSUPPORTED_FOR_ATTACHING_PROFILER.

Metodi con restrizioni di ICorProfilerInfo:

Metodi con restrizioni di ICorProfilerInfo2:

Metodi con restrizioni di ICorProfilerInfo3:

Dd997942.collapse_all(it-it,VS.110).gifEsempio di connessione del profiler

In questo esempio si presuppone che il processo trigger conosca già l'identificatore (PID) del processo di destinazione, il numero di millisecondi di attesa per il timeout, il CLSID del profiler da caricare e il percorso completo del file DLL del profiler. Si presuppone inoltre che lo sviluppatore del profiler abbia definito una struttura denominata MyProfilerConfigData, contenente i dati di configurazione per il profiler, e una funzione denominata PopulateMyProfilerConfigData, che inserisce i dati di configurazione in tale struttura.

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;      
}

Dd997942.collapse_all(it-it,VS.110).gifInizializzazione del profiler dopo la connessione

A partire da .NET Framework 4, il metodo ICorProfilerCallback3::InitializeForAttach viene fornito come controparte di connessione del metodo ICorProfilerCallback::Initialize. CLR chiama InitializeForAttach per dare al profiler una possibilità di inizializzare il proprio stato dopo un'operazione di connessione. In questo callback, il profiler chiama il metodoICorProfilerInfo::SetEventMask per richiedere uno o più eventi. A differenza delle chiamate effettuate durante il metodo ICorProfilerCallback::Initialize, le chiamate al metodo SetEventMask effettuate da InitializeForAttach possono impostare solo bit appartenenti alla maschera di bit COR_PRF_ALLOWABLE_AFTER_ATTACH (vedere Restrizioni della connessione).

Quando InitializeForAttach restituisce un codice di errore, CLR registra l'errore nel log eventi dell'applicazione di Windows, rilascia l'interfaccia di callback del profiler e scarica il profiler. AttachProfiler restituisce lo stesso codice di errore di InitializeForAttach.

Dd997942.collapse_all(it-it,VS.110).gifCompletamento della connessione del profiler

A partire da .NET Framework 4, il callback ICorProfilerCallback3::ProfilerAttachComplete viene generato dopo il metodo InitializeForAttach. ProfilerAttachComplete indica che i callback richiesti dal profiler nel metodo InitializeForAttach sono stati attivati e che il profiler può ora eseguire l'aggiornamento degli ID associati senza considerare eventuali notifiche mancanti.

Si supponga ad esempio che il profiler abbia impostato COR_PRF_MONITOR_MODULE_LOADS durante il callbackInitializeForAttach. Quando il profiler restituisce un risultato da InitializeForAttach, CLR abilita i callback ModuleLoad, quindi genera il callback ProfilerAttachComplete. Il profiler può usare quindi il metodo ICorProfilerInfo3::EnumModules per enumerare i ModuleID per tutti i moduli attualmente caricati durante il callback ProfilerAttachComplete. Inoltre, vengono generati eventi ModuleLoad per tutti i nuovi moduli caricati durante l'enumerazione. I profiler dovranno gestire correttamente gli eventuali duplicati incontrati. Ad esempio, un modulo che viene caricato proprio mentre un profiler si sta connettendo può generare un ModuleID duplicato: nell'enumerazione restituita da ICorProfilerInfo3::EnumModules e in un callback ModuleLoadFinished.

Disconnessione di un profiler

La richiesta di connessione deve essere avviata out-of-process dal processo trigger. La richiesta di disconnessione viene invece avviata in-process dalla DLL del profiler quando chiama il metodo ICorProfilerInfo3::RequestProfilerDetach. Se lo sviluppatore del profiler desidera avviare la richiesta di disconnessione out-of-process, ad esempio da un'interfaccia utente personalizzata, dovrà creare un meccanismo di comunicazione tra processi per segnalare alla DLL del profiler (in esecuzione insieme all'applicazione di destinazione) di chiamare RequestProfilerDetach.

RequestProfilerDetach esegue parte delle operazioni, inclusa l'impostazione del proprio stato interno per impedire l'invio di eventi alla DLL del profiler, in modo sincrono prima di restituire un risultato. Le altre operazioni vengono eseguite in modo asincrono, dopo che RequestProfilerDetach ha restituito un risultato. Queste operazioni rimanenti vengono eseguite su un thread distinto (DetachThread) e includono il polling e la verifica che tutto il codice del profiler sia stato estratto dagli stack di tutti i thread dell'applicazione. Al termine di RequestProfilerDetach, il profiler riceve un callback finale (ICorProfilerCallback3::ProfilerDetachSucceeded), prima che CLR rilasci gli heap dell'interfaccia e del codice del profiler, quindi scarica la DLL del profiler.

Dd997942.collapse_all(it-it,VS.110).gifConsiderazioni sui thread durante la disconnessione

È possibile passare il controllo dell'esecuzione a un profiler in molti modi. Questa operazione non deve essere tuttavia eseguita dopo che il profiler è stato scaricato e sia il profiler che il runtime devono condividere la responsabilità di assicurarsi che questo comportamento non si verifichi:

  • Il runtime non conosce i thread creati o controllati dal profiler che contengono o potranno contenere il codice del profiler sullo stack. Il profiler deve pertanto assicurarsi di uscire da tutti i thread creati e deve arrestare tutte le operazioni di campionamento o controllo prima di chiamare ICorProfilerInfo3::RequestProfilerDetach. Questa regola ha un'unica eccezione: il profiler può usare un thread creato per chiamare ICorProfilerInfo3::RequestProfilerDetach. Questo thread deve tuttavia mantenere il proprio riferimento alla DLL del profiler attraverso le funzioni LoadLibrary e FreeLibraryAndExitThread (vedere la sezione successiva per i dettagli).

  • Dopo che il profiler ha chiamato RequestProfilerDetach, il runtime deve assicurarsi che i metodi ICorProfilerCallback non facciano in modo che il codice del profiler rimanga sullo stack di un thread quando il runtime tenta di scaricare completamente il profiler.

Dd997942.collapse_all(it-it,VS.110).gifProcedura dettagliata per la disconnessione

I passaggi seguenti si verificano quando un profiler viene disconnesso:

  1. Il profiler esce da tutti i thread che ha creato e arresta tutte le operazioni di campionamento e controllo prima di chiamare ICorProfilerInfo3::RequestProfilerDetach, con l'eccezione seguente:

    Un profiler può implementare la disconnessione tramite uno dei propri thread per chiamare il metodo ICorProfilerInfo3::RequestProfilerDetach, anziché usare un thread creato da CLR. Se un profiler implementa questo comportamento, è accettabile che questo thread del profiler esista quando viene chiamato il metodo RequestProfilerDetach, in quanto si tratta del thread che chiama il metodo. Tuttavia, se un profiler sceglie questa implementazione, deve garantire le condizioni seguenti:

    • Il thread che rimane per chiamare RequestProfilerDetach deve mantenere il proprio riferimento alla DLL del profiler, chiamando autonomamente la funzione LoadLibrary.

    • Dopo aver chiamato RequestProfilerDetach, il thread deve chiamare immediatamente la funzione FreeLibraryAndExitThread per rilasciare il controllo sulla DLL del profiler e uscire.

  2. RequestProfilerDetach imposta lo stato interno del runtime in modo che il profiler venga considerato come disabilitato. In tal modo vengono evitate chiamate future nel profiler tramite i metodi di callback.

  3. RequestProfilerDetach segnala a DetachThread di iniziare a controllare che tutti i thread abbiano estratto eventuali metodi di callback restanti dai relativi stack.

  4. RequestProfilerDetach restituisce un codice di stato che indica se la disconnessione è stata avviata correttamente.

    A questo punto, CLR impedisce ulteriori chiamate dal profiler in CLR tramite i metodi di interfaccia ICorProfilerInfo, ICorProfilerInfo2 e ICorProfilerInfo3. Tali chiamate generano immediatamente un errore e restituiscono un HRESULT CORPROF_E_PROFILER_DETACHING.

  5. Il profiler restituisce un risultato o esce dal thread. Se ha usato uno dei propri thread per effettuare la chiamata a RequestProfilerDetach, il profiler deve chiamare subito FreeLibraryAndExitThread da tale thread. Se il profiler ha chiamato RequestProfilerDetach tramite un thread CLR, ovvero dall'interno di un callback, restituirà semplicemente il controllo a CLR.

  6. Nel frattempo, DetachThread continua a controllare se tutti i thread hanno estratto eventuali metodi di callback restanti dai relativi stack.

  7. Quando DetachThread determina che non rimane alcun callback sullo stack di qualsiasi thread, chiama il metodo ICorProfilerCallback3::ProfilerDetachSucceeded. Il profiler deve eseguire un lavoro minimo nel metodo ProfilerDetachSucceeded e restituire rapidamente un risultato.

  8. DetachThread esegue un'operazione Release() finale sull'interfaccia ICorProfilerCallback del profiler.

  9. DetachThread chiama la funzione FreeLibrary nella DLL del profiler.

Dd997942.collapse_all(it-it,VS.110).gifRestrizioni della disconnessione

La disconnessione non è supportata nei seguenti scenari:

  • Quando il profiler imposta flag dell'evento non modificabili.

  • Quando il profiler usa i probe enter/leave/tailcall (ELT).

  • Quando il profiler usa la strumentazione MSIL (Microsoft Intermediate Language), ad esempio chiamando il metodo SetILFunctionBody.

Se si tenta di disconnettere il profiler in questi scenari, RequestProfilerDetach restituisce un errore HRESULT.

Debug

Le funzionalità di connessione e disconnessione del profiler non impediscono il debug di un'applicazione. È possibile eseguire la connessione e la disconnessione di un profiler a un'applicazione in qualsiasi momento durante la sessione di debug. Un'applicazione in cui viene eseguita una connessione al profiler (e in seguito una disconnessione) può eseguire anche la connessione e la disconnessione di un debugger in qualsiasi momento. Non è tuttavia possibile profilare un'applicazione sospesa da un debugger perché non può rispondere a una richiesta di connessione del profiler.

Vedere anche

Altre risorse

Cenni preliminari sulla profilatura

Profilatura (riferimenti alle API non gestite)

Riferimenti alle API non gestite