jdb is the Java source debugger. It is not the most
powerful source debugger around, but it is certainly better than
nothing. Here is a trace of a jdb session with
annotations:
- First, make sure that you compile all your code with the
-g option. This ensures that debugging information
will be put in the .class files.
- Next invoke the debugger on the program you want to debug.
Notice that in this case I start on the Acme server, although I want
to debug my own servlet.
gaelic 3/home/keane/cs336/edu/rutgers/keane/cs336>javac -g *.java
gaelic 3/home/keane/cs336/edu/rutgers/keane/cs336>jdb Acme.Serve.Serve
Initializing jdb...
0xb0:class(Acme.Serve.Serve)
- The ? command will list everything
jdb will do. The commands you will use most are:
- where to show the stack and figure out where
you are.
- print to print a variable's value out.
- locals to see all the local values in the
current stack frame.
- stop to set a breakpoint in your code so the
debugger will stop there.
- up to go up the stack (look at the caller of
the current frame).
- down to go down the stack (look at the callee
of the current frame).
- step to debug into the next function called
(that is, continue execution and debugging into the next called
function).
- next to step over the next function called
(that is, execute the current line, but stay in the function).
- step up continue the current function until it
returns to its caller.
- cont continue execution.
- list show the source code at the current line.
- use set the source search path root.
> ?
** command list **
threads [threadgroup] -- list threads
thread <thread id> -- set default thread
suspend [thread id(s)] -- suspend threads (default: all)
resume [thread id(s)] -- resume threads (default: all)
where [thread id] | all -- dump a thread's stack
wherei [thread id] | all -- dump a thread's stack, with pc info
threadgroups -- list threadgroups
threadgroup <name> -- set current threadgroup
print <id> [id(s)] -- print object or field
dump <id> [id(s)] -- print all object information
locals -- print all local variables in current stack frame
classes -- list currently known classes
methods <class id> -- list a class's methods
stop in <class id>.<method>[(argument_type,...)] -- set a breakpoint in a method
stop at <class id>:<line> -- set a breakpoint at a line
up [n frames] -- move up a thread's stack
down [n frames] -- move down a thread's stack
clear <class id>.<method>[(argument_type,...)] -- clear a breakpoint in a method
clear <class id>:<line> -- clear a breakpoint at a line
step -- execute current line
step up -- execute until the current method returns to its caller
stepi -- execute current instruction
next -- step one line (step OVER calls)
cont -- continue execution from breakpoint
catch <class id> -- break for the specified exception
ignore <class id> -- ignore when the specified exception
list [line number|method] -- print source code
use [source file path] -- display or change the source path
memory -- report memory usage
gc -- free unused objects
load classname -- load Java class to be debugged
run <class> [args] -- start execution of a loaded Java class
!! -- repeat last command
help (or ?) -- list commands
exit (or quit) -- exit debugger
- Let's see what classes were automatically loaded with
Acme.Serve.Serve:
> classes
** classes list **
0x7:class(java.lang.NoClassDefFoundError)
0x3:class(java.lang.Class)
0x2:class(java.lang.Object)
0x8:class(java.lang.Throwable)
0x9:interface(java.io.Serializable)
0x4:class(java.lang.String)
0xa:interface(java.lang.Comparable)
:
Goes on for pages...
:
0xd3:class(java.lang.RuntimePermission)
0xb0:class(Acme.Serve.Serve)
0xb2:interface(Acme.Serve.servlet.ServletContext)
- We can also find out what methods are known in a class:
> methods edu.rutgers.keane.cs336.Login
void <init>()
void service(Acme.Serve.servlet.http.HttpServletRequest, Acme.Serve.servlet.http.HttpServletResponse)
> methods edu.rutgers.keane.cs336.Session
void <clinit>()
void <init>()
java.util.Random access$0()
java.lang.String getSession(java.lang.String)
java.lang.String makeSession(java.lang.String)
- Now I set a breakpoint at the beginning of the
Login.service() method. I want the debugger to stop as soon
as it starts to process a user login:
> stop in edu.rutgers.keane.cs336.Login.service
Breakpoint set in edu.rutgers.keane.cs336.Login.service
- Now I'll start running the server. After I start it, I will
bring up the login form on my brower (you'll have to use your
imagination here...), enter my userid and password and
click enter. As soon as I do, the debugger stops at the beginning of
the Login.service() method:
> run Acme.Serve.Serve -p 1234
running ...
main[1] [Thu Mar 01 11:09:31 EST 2001] getting /login.html
Breakpoint hit: edu.rutgers.keane.cs336.Login.service (Login:56)
- OK. Let's see what variables are in scope at entry:
Thread-2[1] locals
Method arguments:
Local variables:
this = edu.rutgers.keane.cs336.Login@5cbc1b49
req = Acme.Serve.ServeConnection@a2dc1b4e
res = Acme.Serve.ServeConnection@a2dc1b4e
- First I step to the next line. Then I try to list the source,
but jdb is confused:
Thread-2[1] next
Thread-2[1]
Breakpoint hit: edu.rutgers.keane.cs336.Login.service (Login:63)
Thread-2[1] list
Unable to find Login.java
- The problem is that jdb wants to use the class name
as the path to the source. Since I'm debugging the class
edu.rutgers.keane.cs336.Login, it wants to look for the file
(starting from the current directory)
edu/rutgers/keane/cs336/Login.java. However, I started
jdb in edu/rutgers/keane/cs336, so it can't find the
file. I need to tell jdb to start its search from the root
of the classpath:
Thread-2[1] use /home/keane/cs336
Thread-2[1] list
59 }
60
61 /* Get the parameters for the request */
62 try {
63 => if ((parms = HTMLHelper.getPostParms(req)) == null) {
64 res.sendError( HttpServletResponse.SC_BAD_REQUEST );
65 return;
66 }
67 } catch (IOException e) {
- Step a few more times...
Thread-2[1] next
Thread-2[1]
Breakpoint hit: edu.rutgers.keane.cs336.Login.service (Login:82)
Thread-2[1] next
Thread-2[1]
Breakpoint hit: edu.rutgers.keane.cs336.Login.service (Login:83)
Thread-2[1] list
79 // System.out.println("Parameter: '" + key + "' has value '" + value + "'");
80 // }
81
82 String userid = (String) parms.get("userid");
83 => String password = (String) parms.get("password");
84
85 res.setStatus( HttpServletResponse.SC_OK );
86 res.setContentType( "text/html" );
87 ServletOutputStream p = res.getOutputStream();
Thread-2[1] next
Thread-2[1]
Breakpoint hit: edu.rutgers.keane.cs336.Login.service (Login:85)
- Now that we've gone a little farther, let's see what new
variables have come into scope:
Thread-2[1] locals
Method arguments:
Local variables:
this = edu.rutgers.keane.cs336.Login@5cbc1b49
req = Acme.Serve.ServeConnection@a2dc1b4e
res = Acme.Serve.ServeConnection@a2dc1b4e
parms = {password=foobar, userid=123456789}
userid = 123456789
password = foobar
- This is boring. I want to set another breakpoint several
lines on, at line 87. Then I cont to resume execution until
the breakpoint is hit:
Thread-2[1] stop at edu.rutgers.keane.cs336.Login:87
Breakpoint set at edu.rutgers.keane.cs336.Login:87
Thread-2[1] cont
Thread-2[1]
Breakpoint hit: edu.rutgers.keane.cs336.Login.service (Login:87)
Thread-2[1] list
83 String password = (String) parms.get("password");
84
85 res.setStatus( HttpServletResponse.SC_OK );
86 res.setContentType( "text/html" );
87 => ServletOutputStream p = res.getOutputStream();
88
89 /* Read and set database system properties*/
90 try {
91 PropInit.readfile("gradedb.javaprop");
- I want to leave this function and stop when we get back to the
caller:
Thread-2[1] step up
Thread-2[1]
Breakpoint hit: Acme.Serve.ServeConnection.runServlet (ServeConnection:645)
- Let's go up and down the stack and see what lives in each
frame:
Thread-2[1] where
[1] Acme.Serve.ServeConnection.runServlet (ServeConnection:645)
[2] Acme.Serve.ServeConnection.parseRequest (ServeConnection:629)
[3] Acme.Serve.ServeConnection.run (ServeConnection:540)
[4] java.lang.Thread.run (Thread:479)
Thread-2[1] locals
Method arguments:
Local variables:
this = Acme.Serve.ServeConnection@a2dc1b4e
servlet = edu.rutgers.keane.cs336.Login@5cbc1b49
Thread-2[1] up
Thread-2[2] locals
Method arguments:
Local variables:
this = Acme.Serve.ServeConnection@a2dc1b4e
lineBytes = { 0x43, 0x6f, 0x6e, ... }
len = 0
line = Content-length: 32
tokens = { POST, /Login, HTTP/1.0 }
qmark = -1
servlet = edu.rutgers.keane.cs336.Login@5cbc1b49
Thread-2[2] up
Thread-2[3] locals
Method arguments:
Local variables:
this = Acme.Serve.ServeConnection@a2dc1b4e
- We can also show the source at each level as we go back down
the stack:
Thread-2[3] list
536 {
537 problem( "Getting streams: " + e.getMessage(), SC_BAD_REQUEST );
538 }
539
540 => parseRequest();
541
542 try
543 {
544 socket.close();
Thread-2[3] down
Thread-2[2] list
625 reqUriPath = decode( reqUriPath );
626
627 Servlet servlet = (Servlet) serve.registry.get( reqUriPath );
628 if ( servlet != null )
629 => runServlet( (HttpServlet) servlet );
630 }
631 catch ( IOException e )
632 {
633 problem( "Reading request: " + e.getMessage(), SC_BAD_REQUEST );
Thread-2[2] down
Thread-2[1] list
641 setDateHeader( "Date", System.currentTimeMillis() );
642 setHeader(
643 "Server", ServeUtils.serverName + "/" + ServeUtils.serverVersion );
644 setHeader( "Connection", "close" );
645 => try
646 {
647 servlet.service( this, this );
648 }
649 catch ( IOException e )
Thread-2[1] down
End of stack.
- That's it! Now I just cont and the server returns to running:
Thread-2[1] cont
Thread-2[1]