Skip to content

samroberton/bugger-it

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

7 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

bugger-it

A Clojure facility for using the Java Debug Interface (JDI), part of the Java Platform Debugger Architecture (JPDA), to debug a program running in a separate JVM process.

JDI is a powerful interface that allows you to do a lot of things with the JVM you're debugging. Most debuggers use that power to provide fairly standard capabilities: hit a breakpoint, show you the locals, allow you to manually step through code as it executes, etc. But typically they don't give you much ability to programmatically manage the debugger.

The goal of bugger-it is to give you convenient programmatic control over the debugging facilities that JDI provides. You can use that to hit a breakpoint and manually step through code if you want. Or you can write a function that bugger-it will invoke every time your breakpoint is hit, which can gather whatever information you want gathered at the breakpoint, do something useful with it, and automatically resume code execution in JVM being debugged.

That 'do something with it' could just be emitting trace-like logging, or it could be profiling, or it could be conditional installation/disabling of other breakpoints, or state inspection, or hitting up your favourite HTTP interface to order you a pizza online. I don't care. bugger-it's job is to allow you to install a breakpoint, and to invoke your callback handler function when JDI tells us your breakpoint got hit.

I've drawn a lot of inspiration from Hugo Duncan's ritz project. ritz's intent is to utilise JDI specifically to provide an interface for an nREPL middleware to add debugging facilities to editors such as emacs. As a result, ritz's focus is more on managing the JVM and the debug interface for you. You install a breakpoint, and then when it's hit, ritz calls a defmulti which does what ritz does when breakpoints are hit. You could handle it yourself by installing your own defmethod, but I don't think that's really the point of ritz.

The focus for bugger-it at this stage is more on the user sitting at a REPL wanting to programmatically observe the execution of their (or someone else's) code in the remote VM.

Installation

Available from https://github.com/samroberton/bugger-it.

Please note that to use bugger-it, you'll need $JAVA_HOME/lib/tools.jar to be on your classpath. In many environments that'll be the case already, but if you're running it in a plain lein repl, and you see this error or something like it:

#<CompilerException java.lang.ClassNotFoundException: com.sun.jdi.Bootstrap, compiling:(bugger_it/core.clj:1:1)>

then you'll need to fix your classpath to include tools.jar.

I've done that in my environment by editing my ~/.lein/profiles.clj to include tools.jar as a resource when I'm running the repl command:

    {:repl {:resource-paths ["/usr/lib/jvm/java-8-oracle/lib/tools.jar"]}}

Usage

At this stage, bugger-it is intended for connecting to an already-running JVM process. Therefore the JVM process will need to have been started with these options:

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

If you want to be able to inspect local variables in the debuggee, and you're not too worried about running out of memory from lazy seqs holding onto their heads, you'll probably want to start your debuggee JVM process with the following, as well:

    -Dclojure.compiler.disable-locals-clearing=true

If you wish to debug a process that you run from a lein REPL, for example, you can launch the REPL from your debuggee's lein project directory like this:

    $ JAVA_OPTS="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n \
          -Dclojure.compiler.disable-locals-clearing=true" lein repl

When that JVM starts up, you'll see it report the port number to which it's bound its debug listener socket, like so: Listening for transport dt_socket at address: 34617

Then, in a REPL with bugger-it on the classpath and referred as bugger:

    (def debuggee (bugger/connect-to-vm "localhost" 34617))

with whatever hostname and port number are appropriate. (connect-to-vm returns a Debuggee handle for the remote VM which you'll need to do anything useful, so in the above we put into a var we can use in our REPL session.)

Once you're connected, you can install breakpoints like so:

(defn my-handler-function [thread]
  (println "Hooray, my breakpoint got hit in thread " thread))

(bugger/break-at-point debuggee
                       my-handler-fn
                       :bugger-it-examples.fibonacci/stupid-fib
                       7
                       {:suspend :none})

Then, when your breakpoint is hit, the remote VM will continue executing and my-handler-function will be invoked in your REPL session's VM.

Examples

...

Bugs

No doubt legions of them. Feel free to report them via github.

License

Copyright © 2014 Sam Roberton

Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published