Compartir a través de


Asociar y desasociar un generador de perfiles

En versiones de .NET Framework anteriores a .NET Framework versión 4, una aplicación y su generador de perfiles tenían que cargarse al mismo tiempo. Cuando una aplicación se iniciaba, el runtime determinaba si se debían generar perfiles de la aplicación examinando variables de entorno y valores del Registro, y, a continuación, cargaba el generador de perfiles necesario. El generador de perfiles permanecía en el espacio del proceso hasta que la aplicación salía.

A partir de .NET Framework 4, puede asociar un generador de perfiles a una aplicación después de que la aplicación se haya iniciado, generar perfiles y, a continuación, desasociar el generador de perfiles antes de que la aplicación finalice. Una vez desasociado el generador de perfiles, la aplicación continúa ejecutándose igual que antes de que se asociara el generador de perfiles. Es decir, no queda ningún rastro de la presencia temporal del generador de perfiles en el espacio del proceso.

Además, a partir de .NET Framework 4, los métodos para asociar y desasociar perfiles y las mejoras de las API de generación de perfiles permiten utilizar las herramientas basadas en el generador de perfiles como herramientas de diagnóstico Just-In-Time, listo para usar.

Estas mejoras se tratan en las siguientes secciones:

  • Asociar generadores de perfiles

    • Adjuntar Detalles

    • Asociar paso a paso

    • Buscar el método AttachProfiler

    • Asociar a una aplicación de destino antes de que se cargue el runtime

    • Restricciones de asociar

    • Ejemplo de asociar el generador de perfiles

    • Inicializar el generador de perfiles después de asociarlo

    • Completar la operación de asociar el generador de perfiles

  • Desasociar un generador de perfiles

    • Consideraciones sobre los subprocesos durante la desasociación

    • Desasociar paso a paso

    • Restricciones de desasociar

  • Depuración

Asociar generadores de perfiles

Dos procesos independientes son necesarios para asociar un generador de perfiles a una aplicación en ejecución: el proceso desencadenador y el proceso de destino.

  • El proceso desencadenador es un ejecutable que hace que la DLL del generador de perfiles se cargue en el espacio del proceso de una aplicación en ejecución. El proceso desencadenador utiliza el método ICLRProfiling::AttachProfiler para pedir a Common Language Runtime (CLR) que cargue la DLL del generador de perfiles. Cuando la DLL del generador de perfiles se ha cargado en el proceso de destino, el proceso desencadenador puede permanecer o salir, según la decisión del desarrollador del generador de perfiles.

  • El proceso de destino contiene la aplicación que desea perfilar y el CLR. Una vez llamado el método AttachProfiler, la DLL del generador de perfiles se carga en este espacio del proceso junto con la aplicación de destino.

Adjuntar Detalles

AttachProfiler adjunta el generador de perfiles especificado al proceso especificado, pasando de manera opcional algunos datos al generador de perfiles. Espera el tiempo especificado para que se complete el proceso.

Dentro del proceso de destino, el procedimiento de cargar un generador de perfiles cuando se adjunta es similar al procedimiento de cargar un generador de perfiles en el momento de inicio: el CLR CoCreates el CLSID determinado, busca la interfaz ICorProfilerCallback3 y llama al método ICorProfilerCallback3::InitializeForAttach. Si estas operaciones tienen éxito en el tiempo de espera asignado, AttachProfiler devuelve un HRESULT de S_OK. Por consiguiente, el proceso desencadenador debería elegir un tiempo de espera suficiente para que el generador de perfiles complete la inicialización.

NotaNota

Se pueden producir tiempos de espera porque un finalizador del proceso de destino se ejecuta durante más tiempo que el valor de tiempo de espera, lo que da como resultado un (ERROR_TIMEOUT) HRESULT_FROM_WIN32.Si recibe este error, puede reintentar la operación de asociar.

Si el tiempo de espera expira antes de completarse la operación, AttachProfiler devuelve un error HRESULT; sin embargo, la operación de asociar puede haber sido correcta. En casos así, hay maneras alternativas de determinar la corrección. Los desarrolladores del generador de perfiles tienen a menudo un canal de comunicación personalizado entre el proceso desencadenador y la aplicación de destino. Este tipo de canal de comunicación se puede utilizar para detectar una operación de asociar correcta. El proceso desencadenador también puede comprobar la corrección llamando de nuevo a AttachProfiler y recibiendo CORPROF_E_PROFILER_ALREADY_ACTIVE.

Además, el proceso de destino debe tener derechos de acceso suficientes para poder cargar la DLL del generador de perfiles. Esto puede ser un problema cuando se adjunta a los servicios (por ejemplo, el proceso W3wp.exe) que se están ejecutando en cuentas con derechos de acceso restringidos, como el servicio de red. En estos casos, algunos directorios del sistema de archivos pueden tener restricciones de seguridad que impiden que el proceso de destino tenga acceso. Como resultado, es responsabilidad del desarrollador del generador de perfiles instalar la DLL del generador de perfiles en una ubicación que permita el acceso adecuado.

Los errores se registran en el registro de eventos de la aplicación.

Asociar paso a paso

  1. El proceso desencadenador llama a AttachProfiler, identifica el proceso de destino y el generador de perfiles que se va a asociar y opcionalmente pasa los datos que se van a dar al generador una vez asociado.

  2. Dentro del proceso de destino, el CLR recibe la solicitud para asociar.

  3. Dentro del proceso de destino, el CLR crea el objeto de generador de perfiles y obtiene de él la interfaz ICorProfilerCallback3.

  4. A continuación, el CLR llama al método InitializeForAttach, pasa los datos que el proceso desencadenador incluyó en la solicitud de asociar.

  5. El generador de perfiles se inicializa, habilita las devoluciones de llamada que necesita y vuelve correctamente.

  6. Dentro del proceso del desencadenador, AttachProfiler devuelve un HRESULT de S_OK que indica que el generador de perfiles se ha adjuntado correctamente. El proceso desencadenador deja de ser pertinente.

  7. La generación de perfiles continúa de este punto como en las versiones anteriores.

Buscar el método AttachProfiler

Los procesos del desencadenador pueden buscar el método AttachProfiler siguiendo estos pasos:

  1. Llame al método LoadLibrary para cargar mscoree.dll y buscar el método CLRCreateInstance.

  2. Llame al método CLRCreateInstance con los argumentos IID_ICLRMetaHost y CLSID_CLRMetaHost, que devuelve una interfaz ICLRMetaHost.

  3. Llame al método ICLRMetaHost::EnumerateLoadedRuntimes para recuperar una interfaz ICLRRuntimeInfo para cada instancia de CLR en el proceso.

  4. Recorra en iteración las interfaces ICLRRuntimeInfo hasta encontrar la interfaz deseada a la que asociar el generador de perfiles.

  5. Llame al método ICLRRuntimeInfo::GetInterface con un argumento IID_ICLRProfiling para obtener una interfaz ICLRProfiling, que proporciona el método AttachProfiler.

Asociar a una aplicación de destino antes de que se cargue el runtime

Esta funcionalidad no se admite. Si el proceso desencadenador intenta llamar al método AttachProfiler especificando un proceso que el runtime no ha cargado todavía, el método AttachProfiler devuelve un valor HRESULT de error. Si un usuario desea crear el perfil de una aplicación desde el momento que se carga el runtime, debería establecer las variables de entorno adecuadas (o ejecutar una aplicación creada por el desarrollador del generador de perfiles con este fin) antes de iniciar la aplicación de destino.

Restricciones de asociar

Solo está disponible un subconjunto de los métodos ICorProfilerCallback e ICorProfilerInfo para asociar los generadores de perfiles, como se explica en las siguientes secciones.

Restricciones de ICorProfilerCallback

Cuando el generador de perfiles llama al método ICorProfilerInfo::SetEventMask, solo debe especificar las marcas de evento que aparecen en la enumeración COR_PRF_ALLOWABLE_AFTER_ATTACH. No hay otra devolución de llamada disponible para asociar generadores de perfiles. Si un generador de perfiles que se adjunta intenta especificar una marca que no está en la máscara de bits COR_PRF_ALLOWABLE_AFTER_ATTACH, ICorProfilerInfo::SetEventMask devuelve un HRESULT de CORPROF_E_UNSUPPORTED_FOR_ATTACHING_PROFILER.

Restricciones de ICorProfilerInfo

Si un generador de perfiles que se cargó llamando al método AttachProfiler intenta llamar a cualquiera de los métodos ICorProfilerInfo2 o los siguientes ICorProfilerInfo restringidos, ese método devolverá un HRESULT de CORPROF_E_UNSUPPORTED_FOR_ATTACHING_PROFILER.

Los métodos ICorProfilerInfo restringidos:

Los métodos ICorProfilerInfo2 restringidos:

Los métodos ICorProfilerInfo3 restringidos:

Ejemplo de asociar el generador de perfiles

En este ejemplo se supone que el proceso desencadenador ya conoce el identificador de proceso (PID) del proceso de destino, el número de milisegundos que desea como tiempo de espera, el CLSID del generador de perfiles que se carga y la ruta de acceso completa al archivo DLL del generador de perfiles. También se supone que el desarrollador del generador de perfiles ha definido una estructura llamada MyProfilerConfigData, que retiene los datos de configuración para el generador de perfiles, y una función llamada PopulateMyProfilerConfigData, que coloca los datos de configuración en esa estructura.

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

Inicializar el generador de perfiles después de asociarlo

A partir de .NET Framework 4, el método ICorProfilerCallback3::InitializeForAttach se proporciona como el homólogo adjunto del método ICorProfilerCallback::Initialize. El CLR llama a InitializeForAttach para dar al generador de perfiles una oportunidad de inicializar su estado después de una operación de asociar. En esta devolución de llamada, el generador de perfiles llama al método ICorProfilerInfo::SetEventMask para solicitar uno o más eventos. Las llamadas diferentes realizadas durante el método ICorProfilerCallback::Initialize, las llamadas al método SetEventMask hechas de InitializeForAttach solo pueden establecer bits que están en la máscara de bits COR_PRF_ALLOWABLE_AFTER_ATTACH (vea Restricciones de asociar).

Al recibir un código de error de InitializeForAttach, el CLR registra el error en el registro de eventos de la aplicación Windows, libera la interfaz de devolución de llamada del generador de perfiles y descarga el generador de perfiles. AttachProfiler devuelve el mismo código de error que InitializeForAttach.

Completar la operación de asociar el generador de perfiles

A partir de .NET Framework 4, la devolución de llamada ICorProfilerCallback3::ProfilerAttachComplete se emite después del método InitializeForAttach. ProfilerAttachComplete indica que se han activado las devoluciones de llamada que fueron solicitadas por el generador de perfiles en el método InitializeForAttach, y que el generador de perfiles puede ahora alcanzar a los identificadores asociados sin preocuparse por las notificaciones que falten.

Por ejemplo, suponga que el generador de perfiles ha establecido COR_PRF_MONITOR_MODULE_LOADS durante su devolución de llamada InitializeForAttach. Cuando el generador de perfiles vuelve de InitializeForAttach, el CLR habilita las devoluciones de llamada ModuleLoad y, a continuación, emite la devolución de llamada ProfilerAttachComplete. A continuación, el generador de perfiles puede utilizar el método ICorProfilerInfo3::EnumModules para enumerar los ModuleIDs de todos los módulos cargados durante su devolución de llamada ProfilerAttachComplete. Además, se emiten los eventos ModuleLoad para cualquier nuevo módulo que se cargue durante la enumeración. Los generadores de perfiles tendrán que controlar correctamente cualquier duplicado que encuentren. Por ejemplo, un módulo que se carga en el momento correcto mientras se está adjuntando un generador de perfiles puede dar como resultado que ModuleID aparezca dos veces: en la enumeración que devuelve ICorProfilerInfo3::EnumModules y en una devolución de llamada de ModuleLoadFinished.

Desasociar un generador de perfiles

La solicitud de asociar la debe iniciar el proceso desencadenador fuera de proceso. Sin embargo, la solicitud para la desasociación la inicia en proceso la DLL del generador de perfiles cuando llama al método ICorProfilerInfo3::RequestProfilerDetach. Si el desarrollador del generador de perfiles desea iniciar la solicitud de desasociación fuera de proceso (por ejemplo, de una interfaz de usuario personalizada), debe crear un mecanismo de la comunicación entre procesos para señalar a la DLL del generador de perfiles (que se ejecuta junto a la aplicación de destino) que llame a RequestProfilerDetach.

RequestProfilerDetach realiza sincrónicamente parte de su trabajo (incluido establecer su estado interno para impedir el envío de eventos a la DLL del generador de perfiles) antes de volver. El resto de su trabajo lo hace de forma asincrónica, después de que RequestProfilerDetach vuelva. Este trabajo restante se ejecuta en un subproceso independiente (DetachThread) e incluye el sondeo y la comprobación de que todo el código del generador de perfiles se ha sacado de las pilas de todos los subprocesos de la aplicación. Cuando RequestProfilerDetach ha finalizado, el generador de perfiles recibe una última devolución de llamada (ICorProfilerCallback3::ProfilerDetachSucceeded), antes de que el CLR libere los montones de código e interfaz del generador de perfiles, y descarga la DLL del generador de perfiles.

Consideraciones sobre los subprocesos durante la desasociación

El control de ejecución se puede transferir a un generador de perfiles de muchas maneras. Sin embargo, el control no se debe pasar a un generador de perfiles una vez descargado, y el generador de perfiles y el runtime deben compartir la responsabilidad y asegurarse de que este comportamiento no se produce:

  • El runtime no conoce los subprocesos que el generador de perfiles crea o usurpa que contienen, o pueden contener pronto, código del generador de perfiles en la pila. Por consiguiente, el generador de perfiles debe asegurarse de que sale de todos los subprocesos que ha creado, y debe detener todo el muestreo o la usurpación antes de llamar a ICorProfilerInfo3::RequestProfilerDetach. Hay una excepción a esta regla: el generador de perfiles puede utilizar un subproceso que creó para llamar a ICorProfilerInfo3::RequestProfilerDetach. Sin embargo, este subproceso debe mantener su propia referencia a la DLL del generador de perfiles a través de las funciones LoadLibrary y FreeLibraryAndExitThread (consulte la sección siguiente para obtener detalles).

  • Después de que el generador de perfiles llama a RequestProfilerDetach, el runtime debe asegurarse de que los métodos ICorProfilerCallback no causan que el código del generador de perfiles permanezca en la pila de cualquier subproceso cuando el runtime intenta descargar el generador de perfiles totalmente.

Desasociar paso a paso

Los siguientes pasos tienen lugar cuando se desasocia un generador de perfiles:

  1. El generador de perfiles sale de cualquier subproceso que ha creado y detiene todo muestreo y usurpación antes de llamar a ICorProfilerInfo3::RequestProfilerDetach, con la siguiente excepción:

    Un generador de perfiles puede implementar la desasociación utilizando uno de sus propios subprocesos para llamar al método ICorProfilerInfo3::RequestProfilerDetach (en lugar de utilizar un subproceso creado por el CLR). Si un generador de perfiles implementa este comportamiento, es aceptable que este subproceso del generador de perfiles exista cuando se llama al método RequestProfilerDetach (dado que se trata del subproceso que llama al método). Sin embargo, si un generador de perfiles elige esta implementación, debe asegurarse de lo siguiente:

    • El subproceso que permanecerá para llamar a RequestProfilerDetach debe mantener su propia referencia a la DLL del generador de perfiles (llamando a la función LoadLibrary en sí mismo).

    • Después de que el subproceso llame a RequestProfilerDetach, debe llamar inmediatamente a la función FreeLibraryAndExitThread para liberar el bloqueo en la DLL del generador de perfiles y salir.

  2. RequestProfilerDetach establece el estado interno del runtime para que considere que el generador de perfiles está deshabilitado. Esto evita llamadas futuras al generador de perfiles a través de los método de devolución de llamada.

  3. RequestProfilerDetach señala DetachThread para empezar a comprobar que todos los subprocesos han sacado los métodos de devolución de llamada restantes de sus pilas.

  4. RequestProfilerDetach vuelve con un código de estado que indica si la desasociación se inició correctamente.

    En este punto, el CLR deniega cualquier llamada del generador de perfiles al CLR a través de los métodos de interfaz ICorProfilerInfo, ICorProfilerInfo2 e ICorProfilerInfo3. Estas llamadas dan inmediatamente error y devuelven un HRESULT de CORPROF_E_PROFILER_DETACHING.

  5. El generador de perfiles vuelve o sale del subproceso. Si el generador de perfiles usó uno de sus propios subprocesos para esta llamada a RequestProfilerDetach, ahora debe llamar a FreeLibraryAndExitThread desde ese subproceso. Si el generador de perfiles llamó a RequestProfilerDetach mediante un subproceso del CLR (por ejemplo, desde una devolución de llamada), el generador devuelve el control al CLR.

  6. Entretanto, DetachThread continúa comprobando si todos los subprocesos han sacado los método de devolución de llamada restantes de sus pilas.

  7. Cuando DetachThread ha determinado que no queda ninguna devolución de llamada en la pila de ningún subproceso, llama al método ICorProfilerCallback3::ProfilerDetachSucceeded. El generador de perfiles debería hacer un trabajo mínimo en el método ProfilerDetachSucceeded y volver rápidamente.

  8. DetachThread realiza el Release() final en la interfaz ICorProfilerCallback del generador de perfiles.

  9. DetachThread llama a la función FreeLibrary en la DLL del generador de perfiles.

Restricciones de desasociar

La desasociación no se admite en los casos siguientes:

  • Cuando el generador de perfiles establece marcas de evento inmutables.

  • Cuando el generador de perfiles usa sondeos enter/leave/tailcall (ELT).

  • Cuando el generador de perfiles usa instrumentación del lenguaje intermedio de Microsoft (MSIL) (por ejemplo, llamando al método SetILFunctionBody).

Si la desasociación del generador de perfiles se intenta en estos escenarios, RequestProfilerDetach devuelve un error HRESULT.

Depuración

La funcionalidad de asociar y desasociar el generador de perfiles no impide que se depure una aplicación. Una aplicación puede asociar y desasociar un generador de perfiles en cualquier momento durante la sesión de depuración. A la inversa, una aplicación que adjunta un generador de perfiles (y después lo desasocia) también puede asociar y desasociar un depurador en cualquier punto. Sin embargo, no se pueden crear perfiles de una aplicación que un depurador ha suspendido porque no puede responder a una solicitud para asociar un generador de perfiles.

Vea también

Conceptos

Información general sobre la generación de perfiles

Otros recursos

Generación de perfiles (Referencia de la API no administrada)

Referencia de la API no administrada