How to Script the Java Debugger Command-Line Tool (Jdb)

How to script the java debugger command-line tool (jdb)?

You may be able to use JRuby to drive your own debugger via the JPDA API.

Where can I find the jdb (java debugger) implementation? Who develops/maintains it?

I believe Oracle develops and maintains the standalone Java debugger, jdb.

As for the jdb source code:

  1. Download "JDK 8 Demos and Samples" from the Java SE Development Kit 8 Downloads page

  2. Unpack the jdk file and then unpack jdk1.8.0_101/demo/jpda/examples.jar

  3. Then navigate to jdk1.8.0_101/demo/jpda/com/sun/tools/example/debug/tty/TTY.java

Or, if you download openjdk or any other jdk implementation, there should be a zip file in its base directory: java-1.8.0-openjdk/src.zip. if you unzip this file you will find TTY.java here: java-1.8.0-openjdk/com/sun/tools/example/debug/tty/TTY.java

I believe TTY.java is what you are looking for.

What are Java command line options to set to allow JVM to be remotely debugged?

I have this article bookmarked on setting this up for Java 5 and below.

Basically run it with:

-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=1044

For Java 5 and above, run it with:

-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=1044

If you want Java to wait for you to connect before executing the application, replace suspend=n with suspend=y.

Enable java remote debug in code

The address property specifies host (optionally) and port (only the port if host is left out). So address=5005 specifies the port 5005 in your case. If you want your program to wait until you connect your debugger, switch suspend=n to suspend=y.

Edit:
Maybe I misunderstood your question. In case you want to enable debugging programmatically, this won't be possible as the debugging facility JPDA is not exposing a Java API nor any other way to start and stop it programmatically.

Debugging in Maven?

Just as Brian said, you can use remote debugging:

mvn exec:exec -Dexec.executable="java" -Dexec.args="-classpath %classpath -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=1044 com.mycompany.app.App"

Then in your eclipse, you can use remote debugging and attach the debugger to localhost:1044.

Debugging a Java program running from Tomcat (JSP)

An alternative solution that may be easier to use than hooking a debugger to Tomcat:

First, take a look at the call stack. At the bottom, you'll see your class named org.sgrp.singer.filters.SingerLoginFilter. The problem lies here, at line 128 of method doFilter.

The first line says org.apache.jasper.JasperException: java.lang.NullPointerException. That means you've used an object whose value is null at line 128 of mentioned class.

Check out that code to find out what could be wrong. Also, add some print/logging statements to your code.

Debugging should be your last resort. You can gather a lot of information just by looking at your stack trace.

Clojure and the Java Debugger

It is possible to debug Clojure bytecode with jdb but it's not very practical (read tedious) and maybe some information is missing to map from the compiled bytecode to the original source files, but I did an small test to verify it works (at least partially, setting breakpoints when entering a method instead).

I'll create a new Clojure project with Leiningen: lein new app demo. Now, I'll update the file src/demo/core.clj with the following contents:

(ns demo.core
(:gen-class))

(defn x2 [n]
(println "Doubling" n)
(let [x (* n 2)]
x))

(defn -main
[& args]
(let [xs (mapv x2 (range 10))]
(doseq [x xs]
(println x))))

Now, let's run lein uberjar to compile the sources to bytecode:

$ lein uberjar
Compiling demo.core
Created /tmp/demo/target/uberjar/demo-0.1.0-SNAPSHOT.jar
Created /tmp/demo/target/uberjar/demo-0.1.0-SNAPSHOT-standalone.jar

I'll inspect the files generated under the target directory:

$ tree target
target
└── uberjar
├── classes
│   ├── demo
│   │   ├── core$fn__173.class
│   │   ├── core$loading__6721__auto____171.class
│   │   ├── core$_main.class
│   │   ├── core$x2.class
│   │   ├── core.class
│   │   └── core__init.class
...

We can see that the compiler uses inner classes (those with core$ in their name) and our function x2 is compiled to a class.

In order to run the Clojure in jdb, we need to construct a classpath that contains our code, the Clojure runtime and, in Clojure 1.10+ also some dependencies of the Clojure runtime (Spec). You can borrow most of the routes by looking at the output of lein classpath:

$ lein classpath
/tmp/demo/test:/tmp/demo/src:/tmp/demo/dev-resources:/tmp/demo/resources:/tmp/demo/target/default/classes:/home/denis/.m2/repository/org/clojure/clojure/1.10.1/clojure-1.10.1.jar:/home/denis/.m2/repository/org/clojure/spec.alpha/0.2.176/spec.alpha-0.2.176.jar:/home/denis/.m2/repository/org/clojure/core.specs.alpha/0.2.44/core.specs.alpha-0.2.44.jar:/home/denis/.m2/repository/nrepl/nrepl/0.7.0/nrepl-0.7.0.jar:/home/denis/.m2/repository/clojure-complete/clojure-complete/0.2.5/clojure-complete-0.2.5.jar

I will remove some of these JARs and build my classpath to run jdb with the class demo.core which I know is the entry point:

$ jdb -classpath target/uberjar/classes:/home/denis/.m2/repository/org/clojure/clojure/1.10.1/clojure-1.10.1.jar:/home/denis/.m2/repository/org/clojure/spec.alpha/0.2.176/spec.alpha-0.2.176.jar:/home/denis/.m2/repository/org/clojure/core.specs.alpha/0.2.44/core.specs.alpha-0.2.44.jar demo.core

Before running jdb, I want to put a breakpoint somewhere to validate. The x2 function should be a good starting point, but we need to inspect the bytecode a little to understand where in the bytecode to put the breakpoint. Using javap will give us some clues:

$ javap -l target/uberjar/classes/demo/core\$x2.class 
Compiled from "core.clj"
public final class demo.core$x2 extends clojure.lang.AFunction {
public demo.core$x2();
LineNumberTable:
line 4: 0

public static java.lang.Object invokeStatic(java.lang.Object);
LineNumberTable:
line 4: 0
line 6: 26
LocalVariableTable:
Start Length Slot Name Signature
30 3 1 x Ljava/lang/Object;
0 33 0 n Ljava/lang/Object;

public java.lang.Object invoke(java.lang.Object);
LineNumberTable:
line 4: 3

public static {};
LineNumberTable:
line 4: 0
}

From the above, I'll make a note to set a breakpoint in the method demo.core$x2.invokeStatic which is notable because it has local variables. Now we start jdb with the line from before:

$ jdb -classpath target/uberjar/classes:/home/denis/.m2/repository/org/clojure/clojure/1.10.1/clojure-1.10.1.jar:/home/denis/.m2/repository/org/clojure/spec.alpha/0.2.176/spec.alpha-0.2.176.jar:/home/denis/.m2/repository/org/clojure/core.specs.alpha/0.2.44/core.specs.alpha-0.2.44.jar demo.core
Initializing jdb ...
>

In the prompt, I'll tell jdb to stop in the relevant method with stop in demo.core$x2.invokeStatic. You can use the rest of the jdb commands to step, continue and display local values as in the following session:

> stop in demo.core$x2.invokeStatic
Deferring breakpoint demo.core$x2.invokeStatic.
It will be set after the class is loaded.
> run
run demo.core
Set uncaught java.lang.Throwable
Set deferred uncaught java.lang.Throwable
>
VM Started: Set deferred breakpoint demo.core$x2.invokeStatic

Breakpoint hit: "thread=main", demo.core$x2.invokeStatic(), line=4 bci=0

main[1] locals
Method arguments:
n = instance of java.lang.Long(id=2743)
main[1] print n
n = "0"
main[1] cont
> Doubling 0

Breakpoint hit: "thread=main", demo.core$x2.invokeStatic(), line=4 bci=0

main[1] locals
Method arguments:
n = instance of java.lang.Long(id=2749)
Local variables:
main[1] print n
n = "1"
clear demo.core$x2.invokeStatic
Removed: breakpoint demo.core$x2.invokeStatic
main[1] cont
...
> Doubling 2
...
Doubling 9
0
2
4
...
16
18

The application exited

During development, this style is not comparable to the interactive experience of submitting code to a running REPL session and getting instant feedback, so it's not practical (except for very specific scenarios).

I think this is also the type of experience we had in a former team when we debugged Clojure apps vith JDWP in Eclipse, but after a while it becomes hard to track what methods in the Java bytecode map to which functions in your Java code.

How to debug a JUnit test case with jdb?

Sorry folks, I'm new to all of this and I made a silly mistake. I forgot to use the -g command when calling javac to generate my test case class file. It works now.



Related Topics



Leave a reply



Submit