Cómo escribir su propio depurador para Java 47;scala
JDWP (Java Debugger Wire Protocol, Java Debugger Wire Protocol) se utiliza para solicitar y recibir eventos (como cambios de estado o excepciones en subprocesos) a través de mensajes binarios entre el depurador y el programa depurado). , estos mensajes suelen estar conectados en red. La idea detrás de esta arquitectura es desacoplar los dos programas tanto como sea posible. El propósito de esto es reducir el efecto Heisenberg causado por el compilador que cambia la ejecución del código de destino en tiempo de ejecución (Werner es el físico alemán, no su chef favorito, Werner).
Eliminar la mayoría de las operaciones lógicas de depuración del programa de destino ayuda a detectar cambios de estado en la máquina virtual depurada (por ejemplo, GC o OutOfMemoryErrors) que no están dentro del alcance de la depuración. Para mayor comodidad, el JDK viene con JDI (Java Debug Interface), que proporciona una implementación integral del protocolo para depuración, así como la capacidad de manipular el estado de la máquina virtual de destino de manera completa, incluyendo: conexión, desconexión, arranque, y procesamiento.
El compilador de Eclipse utiliza el protocolo JDWP. Al depurar un programa JAVA en el IDE (entorno de desarrollo integrado), si observa los parámetros de la línea de comando pasados al programa en ese momento, encontrará que Eclipse lo hará. pase parámetros adicionales al programa (- agentlib:jdwp =transport=dt_socket,...) para iniciar la depuración de la máquina virtual Java y también determinar el puerto donde se envían las solicitudes y eventos.
Interfaz de programación JVMTI
Una serie de API nativas son el segundo componente clave de la JVM moderna. Cubren una amplia gama de operaciones de JVM, la más famosa de las cuales es la herramienta JVM. Interfaz (es decir, JVMTI). A diferencia de JDWP, JVMTI tiene como objetivo proporcionar una versión C/C++ de la API y un mecanismo para que la JVM cargue dinámicamente archivos de biblioteca precompilados (como .dull, etc.) utilizando los comandos proporcionados por la API.
JVMTI se usa de manera diferente a JDWP en que en realidad ejecuta el compilador en el programa de destino. Este enfoque facilita que el depurador mejore el código del programa tanto en términos de rendimiento como de estabilidad. Sin embargo, su ventaja clave es la capacidad de interactuar directamente con la JVM casi en tiempo real.
Como se puede ver en la serie de API potentes y fáciles de usar proporcionadas por JVMTI, JVMTI está muy dispuesto a explorar y analizar cómo funciona y qué se puede lograr con estas API. Puede obtener los archivos de encabezado API desde el propio JVMTI del JDK.
Escribir una biblioteca de depurador
Escribir su propio depurador requiere crear una biblioteca nativa del sistema operativo en C++. El método principal debería verse así:
JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *vm,char*options,void*)
Este método se llama cuando la JVM carga el agente de depuración. El puntero de la máquina virtual Java pasado es crucial y proporcionará todo el peso necesario para manejar la JVM. Este puntero se puede importar desde la máquina virtual Java a la clase jvmtiEnv; el método GetEnv se puede utilizar para interactuar con la capa JVMTI utilizando los conceptos de capacidades y eventos.
Funciones JVMTI
El punto más crítico al escribir un depurador es comprender claramente las funciones del código del depurador en el programa de destino, especialmente cuando la biblioteca de clases del depurador local que ejecuta el código es diferente del programa en ejecución cuando está estrechamente conectado. Para controlar mejor el impacto del depurador en la ejecución del código, JVMTI introduce el concepto de capacidades.
Al escribir su propio compilador, puede informar a la JVM con antelación sobre un conjunto de comandos API o eventos que pretende utilizar (por ejemplo, establecer puntos de interrupción, interrumpir subprocesos). De esta manera, la JVM puede prepararse para estos comandos o eventos con anticipación y usted puede tener más control sobre la sobrecarga del tiempo de ejecución del depurador.
Este enfoque también permite que las JVM de diferentes fabricantes le indiquen mediante programación qué comandos API son compatibles con toda la especificación JVMTI.
El rendimiento de las funciones varía ampliamente. Algunas características tienen una sobrecarga de rendimiento menor, pero algunas de las características más interesantes tienen lo contrario, como: can_generate_exception_events (genera excepciones en el código para recibir devoluciones de llamada) o can_generate_monitor_events (requiere un bloqueo para recibir devoluciones de llamada).
JVM optimiza el código durante la compilación porque estas características impiden que la JVM optimice el código al compilar dentro del alcance del JIT y obligan a la JVM a pasar al modo interpretado en tiempo de ejecución.
Otras características, como can_generate_field_modification_events para. recibir notificaciones cuando se establece un campo de objeto de destino, genera más gastos generales y hace que el código se ejecute muy lentamente. Desafortunadamente, algunas características de HotSpot no admiten esto, aunque la JVM admite la carga de varias bibliotecas nativas al mismo tiempo (como can_suspend. para suspender y reactivar subprocesos) solo puede ser invocado por una biblioteca a la vez.
Un problema que debe resolverse al crear el depurador de producción de Takipi es cómo hacerlo sin incurrir en muchos gastos generales. Funcionalidades similares. está disponible a continuación (más caro en una versión futura).
Establecer devoluciones de llamada Después de recibir un conjunto de funciones, configure las devoluciones de llamada que llamará la JVM para informarle lo que realmente está sucediendo. proporcione información detallada sobre lo que sucedió. Por ejemplo, una devolución de llamada de excepción incluirá la ubicación del código de bytes donde se lanzó la excepción, el hilo, el objeto de excepción, si se detectó la excepción y la ubicación donde se detectó la excepción. p>
voidJNICALL ExceptionCallback(jvmtiEnv *jvmti,JNIEnv *jni, hilo jthread, método jmethodID,
ubicación jlocation, excepción de proyecto de trabajo,jmethodID catch_method,jlocation catch_location)
Es Vale la pena señalar que la función La sobrecarga generalmente se divide en dos partes, la primera parte proviene de hacer que funcione porque requiere que el compilador JIT compile la transacción de manera diferente para que pueda acceder al código. La otra parte proviene de cuando se realizan las devoluciones de llamada. habilitado, lo que hace que la JVM esté inactiva durante la ejecución Selecciona una ruta de ejecución de bajo rendimiento a través de la cual la función puede acceder al código, durante la cual se incurre en una sobrecarga adicional debido a la compresión y el paso de datos importantes
Puntos de interrupción. y comprobaciones proporcionadas por el compilador Funciones familiares para comprobar estados específicos en tiempo de ejecución, como SetBreakpoint (notificar a la JVM a través de un código de bytes específico para interrumpir la ejecución) o SetFieldModificationWatch (interrumpir la ejecución cada vez que cambia un campo). Para hacer esto, también puedes usar otras funciones complementarias como GetStackTrace y GetThreadInfo para comprender e informar dónde te encuentras en tu código.
Gran parte de la funcionalidad de JVMTI involucra clases y métodos que usan abstracciones, las más familiares son jmethodID y jclass (si ha escrito código de interfaz nativo Java). Además, se proporcionan algunas funciones adicionales, como GetMethodName y GetClassSignature, para ayudar a obtener el nombre del símbolo real del grupo de constantes de clase. Luego puede utilizar estos nombres simbólicos para registrarlo de forma legible por humanos en un archivo de registro o mostrarlo en una interfaz, como lo vería todos los días en un entorno de desarrollo integrado.
Conectar el depurador
Una vez que comienzas a escribir una biblioteca de depurador, la siguiente tarea es conectarla a la JVM. A continuación se muestran varios métodos:
1. Conéctese a JDWP Si está escribiendo un depurador basado en JDWP, debe agregar un parámetro de inicio al objeto depurado para habilitar la depuración en línea.
Su formato es el siguiente: agentlib:jdwp=transport=dt_socket,suspend=y,address=localhost:
2. Conéctese a la biblioteca JVMTI. La JVM cargará la biblioteca de clases JVMTI pasando la línea de comando de la ruta del agente al depurador, apuntando a la ubicación de la biblioteca de clases en el disco duro.
Una alternativa es agregar los argumentos de la línea de comando a la variable de entorno global JAVA_TOOL_OPTIONS, que recibe cada nueva JVM y cuyo valor se agrega automáticamente a la lista de argumentos existente.
3. Conexión remota. También puede conectarse al depurador utilizando la API de conexión remota, una API simple pero poderosa que le permite conectarse a un agente y ejecutar un programa JVM sin iniciar el programa con ningún argumento de línea de comando. La desventaja de esto es que no obtienes funciones normalmente disponibles como can_generate_exception_events, ya que sólo están disponibles cuando se inicia la máquina virtual.