Compartir a través de


Arquitectura de depuración de CLR

La API de depuración de Common Language Runtime (CLR) se diseñó para utilizarse como si formara parte del kernel del sistema operativo. En código no administrado, cuando un programa genera una excepción, el kernel suspende la ejecución del proceso y pasa la información de excepción al depurador utilizando la API de depuración de Win32. La API de depuración de CLR proporciona la misma funcionalidad para el código administrado. Cuando el código administrado genera una excepción, la API de depuración de CLR suspende la ejecución del proceso y pasa la información de excepción al depurador.

Este tema describe cómo y cuando se ve implicada la API de depuración de CLR y qué servicios proporciona.

Arquitectura del proceso

La API de depuración de CLR incluye los dos componentes principales siguientes:

  • La DLL de depuración, que siempre se carga en el mismo proceso como el programa que se depura. El controlador en tiempo de ejecución es responsable de comunicarse con CLR, y de realizar el control de ejecución y la inspección de subprocesos que ejecutan código administrado.

  • La interfaz del depurador, que se carga en un proceso diferente del programa que se depura. La interfaz del depurador es responsable de la comunicación con el controlador en tiempo de ejecución en nombre del depurador. También es responsable de administrar los eventos de depuración de Win32 que proceden del proceso que se está depurando, y de administrar estos eventos o pasarlos a un depurador de código no administrado. La interfaz del depurador es la única parte de la API de depuración de CLR que tiene una API expuesta.

La API de depuración de CLR no proporciona uso remoto entre equipos o entre procesos; es decir, un depurador que usa la API debe hacerlo desde su propio proceso, como se representa en el siguiente diagrama de arquitectura de la API. Esta ilustración muestra dónde se encuentran los diferentes componentes de la API de depuración de CLR, y cómo interactúan con CLR y el depurador.

Arquitectura de la API de depuración de CLR

Arquitectura de depuración de CLR

Depurador de código administrado

Es posible compilar un depurador que solamente admita código administrado. La API de depuración de CLR permite que este tipo de depurador se asocie a un proceso a petición utilizando un mecanismo de asociación flexible. Un depurador con asociación flexible a un proceso puede desasociarse después del proceso.

Sincronización de subprocesos

La API de depuración de CLR tiene requisitos en conflicto relacionados con la arquitectura de proceso. Por una parte, hay muchas razones atractivas para mantener la lógica de depuración en el mismo proceso como el programa que se depura. Por ejemplo, las estructuras de datos son complejas y, con frecuencia, son manipuladas por funciones en lugar de tener diseño de memoria fijo. Es mucho más fácil llamar directamente a las funciones en lugar de intentar descodificar las estructuras de datos desde fuera del proceso. Otra razón para mantener la lógica de depuración en el mismo proceso es mejorar el rendimiento quitando la sobrecarga de comunicación entre procesos. Por último, una característica importante de depuración de CLR es la capacidad de ejecutar código de usuario en proceso con código que está siendo depurado, lo que obviamente requiere cierta cooperación con el proceso del código que está siendo depurado.

Por otro lado, la depuración de CLR debe coexistir con la depuración de código no administrado, que solamente se puede realizar correctamente desde un proceso externo. Además, un depurador fuera de proceso es más seguro que un depurador en proceso, porque la interferencia entre el funcionamiento del depurador y el proceso del código que está siendo depurado se minimiza en un depurador fuera de proceso.

Debido a estos requisitos en conflicto, la API de depuración de CLR combina ambos enfoques. La interfaz de depuración principal está fuera del proceso y coexiste con los servicios de depuración nativos de Win32. Sin embargo, la API de depuración de CLR agrega la capacidad se sincronizarse con el proceso del código que está siendo depurado, para poder ejecutar sin ningún riesgo el código del proceso de usuario. Para realizar esta sincronización, la API colabora con el sistema operativo y CLR para suspender todos los subprocesos del proceso en una ubicación donde no interrumpan una operación y dejar el tiempo de ejecución en un estado incoherente. El depurador, entonces, puede ejecutar código en un subproceso especial que puede examinar el estado del tiempo de ejecución y llamar al código de usuario si es necesario.

Cuando el código administrado ejecuta una instrucción de punto de interrupción o genera una excepción, se notifica al controlador de tiempo de ejecución. Este componente determinará qué subprocesos están ejecutando código administrado y qué subprocesos están ejecutando código no administrado. Normalmente, los subprocesos que están ejecutando código administrado pueden seguir ejecutándose hasta alcanzar un estado donde se pueden suspender sin ningún riesgo. Por ejemplo, pueden tener que completar una recolección de elementos no utilizados que esté en curso. Cuando los subprocesos del código administrado han alcanzado estados seguros, se suspenden todos los subprocesos. La interfaz del depurador informa a continuación al depurador de que se ha recibido un punto de interrupción o una excepción.

Cuando código no administrado ejecuta una instrucción de punto de interrupción o genera una excepción, el componente de interfaz del depurador recibe una notificación a través de la API de depuración de Win32. Esta notificación se pasa a un depurador no administrado. Si el depurador decide que desea realizar la sincronización (por ejemplo, para que se pueda inspeccionar los marcos de pila del código administrado), la interfaz del depurador debe reiniciar primero el proceso del código detenido que está siendo depurado y, a continuación, informar al controlador de tiempo de ejecución para realizar la sincronización. La interfaz del depurador recibe la notificación cuando se ha completado la sincronización. Esta sincronización es transparente para el depurador no administrado.

No se debe permitir que el subproceso que generó la instrucción de punto de interrupción o la excepción se ejecute durante el proceso de sincronización. Para facilitar esto, la interfaz del depurador toma el control del subproceso colocando un filtro de excepciones especial en la cadena del filtro del subproceso. Cuando se reinicia el subproceso, entra en el filtro de excepciones, que colocará el subproceso bajo el control del controlador de tiempo de ejecución. Cuando llega el momento de continuar el procesado de la excepción (o de cancelar la excepción), el filtro devuelve el control a la cadena normal del filtro de excepciones del subproceso o devuelve el resultado correcto para reanudar la ejecución.

En casos raros, el subproceso que genera la excepción nativa puede estar reteniendo bloqueos importantes que se deben liberar para que pueda completarse la sincronización de tiempo de ejecución. (Normalmente serán bloqueos de biblioteca de bajo nivel, tales como bloqueos del montón malloc.) En casos como éste, la operación de sincronización debe expirar y se produce un error en la sincronización. Esto hará que no se produzcan ciertas operaciones que requieren sincronización.

El montón auxiliar en proceso

Dentro de cada proceso de CLR se utiliza un subproceso de aplicación auxiliar de depurador único para garantizar que la API de depuración de CLR funciona correctamente. Este subproceso de aplicación auxiliar es responsable de administrar muchos de los servicios de inspección proporcionados por la API de depuración, además de facilitar la sincronización de subprocesos bajo ciertas circunstancias. Puede utilizar el método ICorDebugProcess::GetHelperThreadID para identificar el subproceso auxiliar.

Interacciones con compiladores JIT

Para permitir a un depurador depurar código compilado Just-In-Time (JIT), la API de depuración de CLR debe poder asignar información de la versión de lenguaje intermedio de Microsoft (MSIL) de una función a la versión nativa de la función. Esta información incluye puntos de secuencia en el código e información de ubicación de variable local. En las versiones 1.0 y 1.1 de .NET Framework, esta información se generaba solamente cuando el motor en tiempo de ejecución estaba en modo de depuración. En .NET Framework 2.0, esta información se genera constantemente.

Además, el código compilado JIT puede optimizarse mucho. Optimizaciones tales como la eliminación de subexpresiones comunes, la expansión en línea de funciones, el desenredo de bucles, código izando, puede conducir etc. a la pérdida de correlación entre el código de MSIL de una función y el código nativo a los que se llamarán para ejecutarlo. Así, estas técnicas agresivas de optimización de código afectan gravemente a la capacidad del compilador JIT para proporcionar información de asignación correcta. Por consiguiente, cuando el motor de tiempo de ejecución se ejecuta en modo de depuración, el compilador JIT no realiza ciertas optimización. Esta restricción permite a los depuradores determinar con precisión la asignación de líneas de código fuente y la ubicación de todas las variables y argumentos locales.

Modos de depuración

La API de depuración de CLR API proporciona modos especiales de depuración en dos casos:

  • Modo Editar y continuar. En este caso, el motor de tiempo de ejecución funciona de manera diferente para que después se pueda modificar el código. Esto se debe a que el diseño de ciertas estructuras de datos en tiempo de ejecución tiene que ser diferente para ser compatible con Editar y continuar. Dado que esto tiene un efecto adverso sobre el rendimiento, no debe utilizar este modo a menos que desee usar la funcionalidad de Editar y continuar.

  • Modo de depuración. Este modo permite al compilador JIT omitir optimizaciones. Por consiguiente, hace que la ejecución de código nativo se ajuste más estrechamente al código fuente del lenguaje de alto nivel. No utilice este modo a menos que sea necesario, porque también tiene un efecto adverso sobre el rendimiento.

Si depura un programa fuera del modo Editar y continuar, no se admitirá la funcionalidad de Editar y continuar. Si depura un programa fuera del modo de depuración, se continuará admitiendo la mayoría de las características de depuración, pero las optimizaciones pueden ocasionar comportamientos extraños. Por ejemplo, puede parecer que el recorrido paso a paso salta aleatoriamente de línea a línea del método y es posible que los métodos en línea no aparezcan en el seguimiento de la pila.

Un depurador puede habilitar tanto el modo de Editar y continuar como el de depuración mediante programación mediante la API de depuración de CLR, si el depurador obtiene el control de un proceso antes de que se haya inicializado el motor de tiempo de ejecución. Esto es suficiente para muchos propósitos. Sin embargo, un depurador asociado a un proceso que ya se haya estado ejecutando durante algún tiempo (por ejemplo, durante la depuración JIT) no podrá iniciar estos modos.

Para ayudar a resolver estos problemas, un programa se puede ejecutar en modo JIT o en modo de depuración independientemente de un depurador. Para obtener información sobre las maneras de habilitar la depuración, vea Depurar, trazar y generar perfiles.

Las optimizaciones JIT pueden hacer que una aplicación sea menos depurable. La API de depuración de CLR permite la inspección de marcos de pila y variables local con código compilado JIT optimizado. Se admite el recorrido paso a paso, pero puede ser impreciso. Puede ejecutar un programa que indique al compilador JIT que desactive todas las optimizaciones JIT para generar código depurable. Para obtener información detallada, vea Facilitar la depuración de una imagen.

Vea también

Conceptos

Información general sobre la depuración en CLR

Otros recursos

Depuración (Referencia de la API no administrada)