rgrunber

Tag: jdt

Hot-Code Replace With The Breakpoint Properties Page in Eclipse

This is a convenient exploitation of how the Breakpoint Properties Page works.

A lot of people are aware of hot-code replacement functionality in the Eclipse JDT (Java Development Tools). This allows one to modify source code while an application is being debugged and have the generated class files be used in the current runtime.

This is great but in the past I have run into issues where this functionality, for some reason fails. This also depends on having editable source files, which doesn’t seem like such a big deal, except that it’s very possible to have source artifacts (fetched by Maven) or source bundles (in the PDE Target Platform) that show up as class files and can’t be (immediately) edited.

So what should you do when you’d like to do a hot-code replace on a class for which you have read-only sources ?

Enter the JDT Breakpoint Properties Page. Generally, this page is meant to help set the conditions on which a breakpoint causes the thread to suspend. For example, you may have some kind of loop that iterates through many values, but only want to investigate what happens on a particular value. Rather than setting a breakpoint in the loop, seeing it suspend on the line, checking the value, hitting resume, over and over until you get the desired value, you could set a condition to suspend when your value is set.

eclipse-breakpoint-properties

So the suspend can be triggered on some condition that will be evaluated prior to executing the line. But there’s nothing stopping us from making the “condition” a code snippet that always returns “false”. The result is a breakpoint that never causes a suspend, but still evaluates the entire code snippet.

One of the simplest things for which this can be used is to insert “printf” statements without actually having to litter them throughout your codebase. Much easier to track and clean :

System.out.println("...");
return false;

With this simple pattern, we can basically emulate something like the CDT’s dynamic printfs .Of course, since this merely inserts code there are some limitations to what can be done versus an actual hot-code replace, but this comes pretty close.

Give Me A Break.. point

A little while ago, someone asked me how Eclipse developers go about debugging the platform. It’s a tricky question to answer because :

  1. I’m not a contributor to any of the “standard” platform projects
  2. Even when debugging the platform, it’s usually only one component (help system, jdt, p2) that you’re ever concerned with at any given time.

Normally I’d probably debug/test changes to a platform bundle just like any other Eclipse plugin :

Launch a child Eclipse instance making sure that the necessary bundles in the workspace are enabled (overriding the ones provided by the platform). However, in this case I’ve decided to craft an example where this will simply not work.

This problem is loosely based on an actual issue that has occured in Fedora’s RPM-packaged Eclipse.

We’ll launch our eclipse instance. You’re probably expecting it to load, with some minor problems.

Nope. Basically, most of the “standard” plugins are not loaded, and so we pretty much have a broken platform. There’s no JDT or PDE either, so good luck launching a child Eclipse instance.

What we can do is launch our broken Eclipse (from within the VM) in remote debug mode, and connect to it from our host machine. We’ll also need to have the right sources checked out. Since the problem seems to be related to the loading of bundles a good place to start is with the simpleconfigurator bundle (org.eclipse.equinox.simpleconfigurator). The last bit of preparation involves checking out the sources that match the bundles we’re using. Most projects will match tag names in their SCM to the version/build qualifier of the released product.

We’ll also open up port 8000 on our VM so that we may connect to it from our host machine. (If security really isn’t an issue, then you can always just bring down iptables)

$ iptables -I INPUT -p TCP -i eth0 --dport 8000 -j ACCEPT
$ eclipse -vmargs -Xdebug -Xrunjdwp:transport=dt_socket,server=y,address=8000

The vmargs flag passes the arguments that follow it, to the JVM.
Basically we’re waiting for a remote client to connect on port 8000 before launching in debug mode.

Now it’s time to set our breakpoints. The simpleconfigurator is a good place to start, so we’ll set a breakpoint when the bundle gets started.

We go back to our host machine and create our java remote debugging configuration. (Run -> Debug Configurations). From here we scroll down to the “Remote Java Application” and create a new one.

I’ve filled in the VM’s IP, and specified port 8000 as the port to which we will be connecting. The last thing to do is to include the actual source as part of the configuration.

As you can see, the simpleconfigurator bundle has already been included (since the project had focus when we created the configuration) so we don’t need to add anything else. We could always add more sources by clicking on “Add”. Now we just hit “Apply”, and then “Debug”.

If you didn’t open up the port correctly, you’d see something like this.

In the Activator’s start() method, the SimpleConfiguratorImpl constructor doesn’t seem to do too much, but applyConfiguration() seems worth exploring, so we’ll step into this function.

The first thing we see is getConfigurationURL() and it’s worth a quick look . Inside, we see that it parses a String representation of a URL. In fact, by just stepping over most of the conditions, we can see that we’re dealing with a file URL, that has a relative path. More specifically we have file URLs for both a user, and shared configuration, and these configurations hold a list of bundles along with various attributes (name, version, location, etc.).  Seems like we’re in the right place.

We can now see that these two configurations (user, and shared) are read, and this is where things get interesting.

This return statement says it all :

return (userBundles.containsAll(sharedBundles)) ? userConfigURL : sharedConfigURL;

If there’s any bundle from the shared configuration, that isn’t present in the user configuration, then the configuration returned is just the shared one. If we copy that expression into the expressions view, we can see it evaluates to false, so we now know this is exactly what’s happening. The Eclipse platform is in fact being properly loaded, but additional installed plug-ins that aren’t part of the shared configuration (like the JDT, PDE, etc.) do not get loaded.

So the question is, which bundle in the shared configuration is not present in the user configuration?

This can be figured out pretty easily. We’ll just add some code to print out which bundle isn’t in the user configuration, and add a new breakpoint on the return call just below. This will allow us to skip to it instead of having to step over all those lines. Now We’ll save the file, and this will restart our debugger from the top of getConfigurationURL().

We resume the program (F8) and it will hit the return call’s breakpoint

So foo.bar.fake is in the shared configuration, but not in the user configuration. Of course there might be many different reasons for why something like this would happen, but for this demonstration, it simply shouldn’t be there, so removing it from the configuration will fix our problem. If we didn’t know the location of the user, or shared configurations, we could easily find them by looking through the defined variables.

We’ll delete this bundle from the shared configuration, and start Eclipse.

Everything is back to normal.

Note that the ugly red background around the minimize/maximize buttons is visible only when using Eclipse from a VM through VNC.