El lado negativo de usar jdb es que sólo hay un interface de línea de comandos, y trata con el mismo código que estámos tratando de depurar. Esto significa que si hay un bug enla máquina virtual Java, jdb se podría equivocar al intentar diagnisticar el mismo bug!
La nueva arquitectura JBUG se creó para resolver estos problemas en el jdb. JBUG, entre otras cosas, proporciona un API de ayuda de depuración en la máquina virtual Java llamado "Java VM Debug Interface" (JVMDI). Este ayudante se comunica con el depurador desde el final usando el "Java Debug Wire Protocol" (JDWP). La depuración desde el final usa el interface remoto "Java Debug Interface" (JDI) para enviar y recibir comando sobre el protocolo JDWP. JBug está disponible para la plataforma Java 2, y tiene un estilo jdb que aprenderemos más adelante.
Luego arrancamos la herramienta jdb con el nombre de la clase del programa como parámetro:javac -g SimpleJdbTest.java
Para depurar un applet en el appletviewer usamos el parámetro -debug como en este ejemplo:jdb SimpleJdbTest Initializing jdb... 0xad:class(SimpleJdbTest)
$ appletviewer -debug MyApplet.html Initializing jdb... 0xee2f9808:class(sun.applet.AppletViewer) >
stop in SimpleJdbTest.<init>
Breakpoint set in SimpleJdbTest.<init>
run
run SimpleJdbTest
running ...
main[1]
Breakpoint hit: SimpleJdbTest.<init>
(SimpleJdbTest:10)
La herramienta jdb se para en la primera línea del constructor. Para listar los método que fueron llamados hasta llegar a este punto de ruptura, introducimos el comando where:
Los métodos numerados de la lista es el último marco de pila que ha alcanzado la JVM. En este caso el último marco de pula es el constructor SimpleJdbTest que fue llamado desde el SimpleJdbTest main.main[1] where [1] SimpleJdbTest.<init> (SimpleJdbTest:10) [2] SimpleJdbTest.main (SimpleJdbTest:29)
Siempre que se llama a un nuevo método, se sitúa en esta lista de pila. La tecnología Hotspot consigue alguna de sus ganancias de velocidad elimando un nuevo marco de pila cuando se llama a un nuevo método. Para obtener una apreciación general de dónde se paró el código, introducimos el comando list.
main[1] list
6 Panel p;
7 Button b;
8 int counter=0;
9
10 SimpleJdbTest() {
11 setSize(100,200);
12 setup();
13 }
14 void setup (){
main[1] list
Unable to find SimpleJdbTest.java
main[1] use book
main[1] list
6 Panel p;
7 Button b[];
8 int counter=0;
9
10 => SimpleJdbTest() {
Pero espera un minuto! Este es ahora el constructor de la clase Frame! Si lo seguimos pasaremos a través del constructor de la clase Frame y no el de la clase SimpleJdbText. Porque SimpleJdbTest desciende de la clase Frame, el constructor padre, que en este caso es Frame, es llamado sin avisarnos.main[1] step main[1] Breakpoint hit: java.awt.Frame.<init> (Frame:222)
main[1] step up
main[1]
Breakpoint hit: SimpleJdbTest.<init>
(SimpleJdbTest:8)
main[1] step
Breakpoint hit: SimpleJdbTest.<init>
(SimpleJdbTest:11)
main[1] list
7 Button b[]=new Button[2];
8 int counter=0;
9
10 SimpleJdbTest() {
11 setSize(100,200);<
12 setup();
13 }
14 void setup (){
15 p=new Panel();
16 }
main[1] next
Breakpoint hit: SimpleJdbTest.<init>
(SimpleJdbTest:12)
main[1] step
Breakpoint hit: SimpleJdbTest.setup (SimpleJdbTest:15)
main[1] list
11 setSize(100,200);
12 setup();
13 }
14 void setup (){
15 => p=new Panel();
16 b[0]= new Button("press");
17 p.add(b[0]);
18 add(p);
19
Esto ocurre porque la línea aún no se ha ejecutado y por lo tanto al campo p no se le ha asignado ningún valor. Necesitamos pasar sobre la sentencia de asignación con el comando next y luego usar de nuevo el comando print p.main[1] print p p = null
main[1] next Breakpoint hit: SimpleJdbTest.setup (SimpleJdbTest:16) main[1] print p p = java.awt.Panel[panel0,0,0,0x0,invalid, layout=java.awt.FlowLayout]
Para seleccionar un punto de ruptura en la clase Button, usamos stop in java.awt.Button.<init>
main[1] stop in java.awt.Button.<init>
java.awt.Button.<init> is overloaded,
use one of the following:
void <init>
void <init>java.lang.String)
El mensaje explica porque jdb no puede parar en este método sin más información, pero el mensaje nos explica que sólo necesitamos ser explícitos en el tipo de retorno para los métodos sobrecargados en los que queremos parar. Para parar en el constructor de Button que crea este Button, usamos stop in java.awt.Button.<init>java.lang.String).
main[1] cont
main[1]
Breakpoint hit: java.awt.Button.<init>
(Button:130)
Si la clase Button no ha sido compilada con información de depurado como se describió antes, no veremos los campos internos desde el comando print.
main[1] clear Current breakpoints set: SimpleJdbTest:10 java.awt.Button:130 main[1] clear java.awt.Button:130 Breakpoint cleared at java.awt.Button: 130
Este ejemplo pone un punto de ruptura en la línea 17 y usa los comandos print y dump para imprimir y volcar el primer objeto Button del array de objetos Button. La salica del comando The dump ha sido abreviada.
main[1] stop at SimpleJdbTest:17
Breakpoint set at SimpleJdbTest:17
main[1] cont
main[1]
Breakpoint hit: SimpleJdbTest.setup (SimpleJdbTest:17)
main[1] print b[0]
b[0] = java.awt.Button[button1,0,0,0x0,invalid,
label=press]
main[1] dump b[0]
b[0] = (java.awt.Button)0x163 {
private int componentSerializedDataVersion = 2
boolean isPacked = false
private java.beans.PropertyChangeSupport
changeSupport = null
long eventMask = 4096
transient java.awt.event.InputMethodListener
inputMethodListener = null
....
java.lang.String actionCommand = null
java.lang.String label = press
}
0xee2f9820:class(SimpleJdbTest) > quit
En Java 2, las cosas son un poco más complicada. Necesitamos decirla a la JVM dónde está el ficheo tools.jar usando la variable CLASSPATH. El fichero tools.jar normalmente se encuentra en el directorio lib de la instalación de la plataforma Java.
También necesitamos desactivar el compilador "Just In Time" (JIT) si existe. Este compilador se desactiva seleccionado la propiedad java.compiler a NONE o a una cadena vacía. Finalmente, como la opción -classpath sobreescribe cualquier classpath seleccionado por el usuario, también necesitamos añadir el CLASSPATH necesario para nuestra aplicación.
Poniéndo todo esto junto, aquí está línea de comandos necesaria para arrancar un programa en modo de depuración remoto. Se pone todo en una sóla línea e incluimos todas las clases necesarias en la línea de comandos.
La salida es el password del agente (en este caso, 4gk5hm) si el programa se arranca de forma satisfactoria. La password de agente se suministra cuando se arranca jdb para que éste peuda encontrar la aplicación arrancada correspondiente en modo depuración en esa máquina.Windows: $ java -debug -classpath C:\java\lib\tools.jar;. -Djava.compiler=NONE SimpleJdbTest Agent password=4gk5hm Unix: $ java -debug -classpath /usr/java/lib/tools.jar:. -Djava.compiler=NONE SimpleJdbTest Agent password=5ufhic
Para arrancar jdb en modo depuración remoto, suministramos un nombre de host, que puede ser la misma máquina donde se está ejecutando el programa o localhost si estámos depurando en la misma máquina que el programa remoto, y la password de agente.
jdb -host localhost -password 4gk5hm
$ jdb -host arsenal -password 5ufhic
Initializing jdb...
> threads
Group system:
1. (java.lang.Thread)0x9 Signal dispatcher
cond. waiting
2. (java.lang.ref.Reference 0xb Reference Handler
$ReferenceHandler) cond. waiting
3. (java.lang.ref. Finalizer
Finalizer cond. waiting
$FinalizerThread)0xd
4. (java.lang.Thread)0xe Debugger agent
running
5. (sun.tools.agent. Breakpoint handler
Handler)0x10 cond. waiting
6. (sun.tools.agent. Step handler
StepHandler)0x12 cond. waiting
Group main:
7. (java.awt. AWT-EventQueue-0
EventDispatchThread) cond. waiting
0x19
8. (sun.awt. PostEventQueue-0
PostEventQueue)0x1b cond. waiting
9. (java.lang.Thread)0x1c AWT-Motif
running
10. (java.lang.Thread)0x1d TimerQueue
cond. waiting
11. (sun.awt. Screen Updater
ScreenUpdater)0x1f cond. waiting
12. (java.lang.Thread)0x20 Thread-0
cond. waiting
> thread 7
AWT-EventQueue-0[1] where
[1] java.lang.Object.wait (native method)
[2] java.lang.Object.wait (Object:424)
[3] java.awt.EventQueue.getNextEvent
(EventQueue:179)
[4] java.awt.EventDispatchThread.run
(EventDispatchThread:67)
AWT-EventQueue-0[1] suspend 7
AWT-EventQueue-0[1] list
Current method is native
AWT-EventQueue-0[1] where
[1] java.lang.Object.wait (native method)
[2] java.lang.Object.wait (Object:424)
[3] java.awt.EventQueue.getNextEvent
(EventQueue:179)
[4] java.awt.EventDispatchThread.run
(EventDispatchThread:67)
AWT-EventQueue-0[1] resume 7
main[1] clear Current breakpoints set: SimpleJdbTest:10 main[1] clear SimpleJdbTest:10 main[1] quit
El fichero jdb.ini nos permite seleccionar los comandos de configuración de jdb, como use, sin tener que introducir los detalles cada vez que ejecutamos jdb. El siguiente fichero de ejemplo jdb.ini empieza una sesión jdb para la clase FacTest. Incluye los fuentes de la plataforma Java en el path de fuentes y le pasa el parámetro número 6 al programa. Se ejecuta y para en la línea 13, muestra la memoria libre, y espera una entrada posterior.jdb -J-Duser.home=.
Aquí está salida de la ejecución del fichero jdb.ini:load FacTest stop at FacTest:13 use /home/calvin/java:/home/calvin/jdk/src/ run FacTest 6 memory
Podríamos peguntarnos si los ficheros jdb.ini pueden usarse para controlar una sesión jdb completa. Desafortunadamente, los comandos en un fichero jdb.ini se ejecutan de forma síncrona, y jdb no espera hasta que se llegue a un punto de ruptuira para ejecutar el siguiente comando. Podemos añadir retardos artificiales con comandos help repetidos, pero no hay garantía de que el thread se suspenda cuando necesitamos que lo haga.$ jdb -J-Duser.home=/home/calvin/java Initializing jdb... 0xad:class(FacTest) Breakpoint set at FacTest:13 running ... Free: 662384, total: 1048568 main[1] Breakpoint hit: FacTest.compute (FacTest:13) main[1]
Para permitir el diario jdb, creamos un fichero llamado .agentLog en el directorio donde estámos ejecutando jdb o java -debug. En el fichero .agentLog, ponemos el nombre del fichero en el que se escriba la información de la sesión en la primera línea.Por ejemplo, un fichero .agentLog podría tener estos contenidos:
Cuando luego ejecutamos jdb o java -debug, veremos que la información de sesión jdb se muestra de esta forma. Podemos usar esta información para recuperar los puntos de ruptura y los comandos introducidos por si necesitamos reproducir esta sesión de depuración.jdblog
---- debug agent message log ---- [debug agent: adding Debugger agent to system thread list] [debug agent: adding Breakpoint handler to system thread list] [debug agent: adding Step handler to system thread list] [debug agent: adding Finalizer to system thread list] [debug agent: adding Reference Handler to system thread list] [debug agent: adding Signal dispatcher to system thread list] [debug agent: Awaiting new step request] [debug agent: cmd socket: Socket[addr=localhost/127.0.0.1, port=38986,localport=3 8985]] [debug agent: connection accepted] [debug agent: dumpClasses()] [debug agent: no such class: HelloWorldApp.main] [debug agent: Adding breakpoint bkpt:main(0)] [debug agent: no last suspended to resume] [debug agent: Getting threads for HelloWorldApp.main]