Bytecode Rewriting

The synchronization events earlier correspond to bytecode in Java class files. In order to log the events, I need to insert calls to a log function (edu.rice.cs.cunit.tm.SyncPointRecorder) around them. Depending on the event, this is done in several ways.

For all of the Thread methods except sleep and yield, this is easy. I rename the method, e.g. start, to start<wrapper>, and then generate a new method with the old name, i.e. start, that makes the necessary log calls and then forwards to the old method. Another way to do this would be to directly insert the calls into the old methods. That is probably faster, but was also more work. If necessary, I’ll change this.

For Thread.sleep and Thread.yield, as well as for all of the Object methods, this was not an option. They are native methods and cannot be renamed, probably because the name must be the same as in some kind of DLL. Directly inserting bytecode is not possible either, of course, since they do not consist of bytecode.

The strategy that I use here is to add wrapper methods, e.g. notify<wrapper> for the notify method in Object, which make the logging calls and then forward. Then I go through all Java class files (user and run-time library!) and replace calls to the original (i.e. to notify) by calls to the wrapper (i.e. notify<wrapper>).

For synchronized blocks, I go through all Java class files and insert log calls immediately before and immediately after MONITORENTER instructions, and another log call immediately before MONITOREXIT instructions.

Synchronized methods, both static and non-static, are handled by inserting a “try to enter” log call before the call site, as well as inserting an “entered” log call at the beginning of the method and “leaving” log calls at every method exit. Unless, of course, the method is native. In that case, a wrapper method is used, just like for the Object methods mentioned above.

I wrote most of this code last summer already. I have high confidence in the code for everything except for synchronized methods. Even that seems to work on user code, but whenever I instrument the entire Java runtime, I get in trouble. Unfortunately, this is hard to debug, since the errors occur before the Java runtime is completely initialized, which prevents the use any kind of debug output or breakpoint. It is rather frustrating.

By using JPDA and not instrumenting all of the Java runtime, we’re currently hoping to make advances.

Share

About Mathias

Software development engineer. Principal developer of DrJava. Recent Ph.D. graduate from the Department of Computer Science at Rice University.
This entry was posted in Concurrent Unit Testing. Bookmark the permalink.

Leave a Reply