The problem
Sometimes programs want to "move" in time. For example, bills are created and after certain period of time it's time to check whether all bills are paid, and if not, start collecting money for unpaid bills. The period between two events can be days, weeks, months or even years. But you wan't to test them right away. Yeah, I know some of you are already thrilling to say something about unit tests, but we are not talking about small components "on the loose" here; we are now talking about full fledged Java EE applications running in Java EE application server.
Needles to say, it's sometimes appropriate to use current time in algorithms and these may well be the problematic snippets of logic. If you change the system time, everything is changed at once, and for example certificates in you appserver may expire (and server stops working). It's no go. Another solution proposed by some of my colleagues is to use custom type (maybe java.util.Date derivative) which is consulting "something" to get requested data offset. However, this latter approach sucks because:
- How does it consult something? How does it differentiate the very same libraries embedded in applications?
- It requires that you own the source code and can modify it (read: you must modify it)
- The code "flows" all the way to production where it's not needed/wanted and you need special protection to keep it disabled in production environment
- It doesn't work with 3rd party components (it's unlikely that they are using your custom type)
The perfect solution would be using just plain java.util.Date, but still be able to adjust time selectively. What I mean by selectively? How about changing the behavior for one application in a cluster and keep the rest intact? Enter BCE!
Using BCE to "move in time"
The idea to use BCE to implement ability to move in time was born after I had a conversation with my colleague. He was suggesting BCE to intercept native calls, but later on (after trying it) I realized it's not the way to go. Instead, I decided to instrument calls "at the client side", meaning I decided to modify all calls themselves instead of just modifying java.lang.System.currentTimeMillis (which is native method). This approach seemed to work and is still in use.
In this posting we go through the trickiest parts of time machine agent. We start with the easiest hook: intercepting calls to java.lang.System.currentTimeMillis. The ASM code intercepting those calls looks like this:
if( opcode
== INVOKESTATIC && owner.equals(
"java/lang/System") &&
name.equals(
"currentTimeMillis" ) ) {
// Generate a currentTimeMillis call
super.visitMethodInsn(
opcode,owner,name,desc );
// Push timeOffset on the stack
visitLdcInsn(
new Long( timeOffset ) );
// Add the two topmost operands
visitInsn(
LADD );
// Only current time remains on the stack
}
What's going on here? We intercept all calls which are static, are invoked against class of type java.lang.System and have method name currentTimeMillis. The very first thing we do is the invocation of actual method. Now the top of the operand stack (TOS) is current time. Then we push time offset on the stack and request addition of two longs (LADD). As a result we have now adjusted current time by the time offset. When this instrumentation code is run on any class calling java.lang.System.currentTimeMillis, it will add given offset to current time. So the following line of code:
System.out.println(
new Date( ) );
actually executes this bytecode:
0: getstatic #32; //Field java/lang/System.out:Ljava/io/PrintStream;
3: invokestatic #47; //Method java/lang/System.currentTimeMillis:()J
6: ldc2_w #20; //long 18000000l
9: ladd
10: invokevirtual #49; //Method java/io/PrintStream.println:(J)V
Note how the time offset, five hours, maps to constant 1800000l, which is then added to current time (moving "current" time five hours to the future).
However, programmers often use more abstract way to ask for current time, for example by constructing an instance of type java.util.Date or calling java.util.Calendar.getInstance. To adjust time returned by default constructor of java.util.Date, we do it like this:
if( opcode == INVOKESPECIAL && owner.equals( "java/util/Date" ) &&
name.equals(
"<init>" ) && desc.equals( "()V" ) ) {
// Bytecode NEW is already executed
// Stack contains now a reference to java.util.Date
(slots reserved 1)
// Note that constructor call is not made yet.
// Dup the reference (slots used +1)
// Stack contains now a reference to java.util.Date
// Note that constructor call is not made yet.
// Dup the reference
// Dup the reference
dup( );
// Now let a constructor call happen (consumes one slot)
super.visitMethodInsn(
opcode,owner,name,desc );
// Dup the reference
dup(
);
// Invoke getTime method
visitMethodInsn(
INVOKEVIRTUAL,"java/util/Date","getTime", "()J");
// Push offset to the stack
visitLdcInsn(
new Long( timeOffset ) );
// Add the operands
visitInsn(
LADD );
// And finally call setTime method
visitMethodInsn(
INVOKEVIRTUAL,"java/util/Date","setTime", "(J)V");
// One reference to java.util.Date remains on the
stack
}
As you can see, the method is now much more complicated. The comments along the lines tell what's going on there, so I don't repeat myself here. And now this line of Java code:
System.out.println( new Date( ) );
produces bytecode:
0: getstatic #32; //Field java/lang/System.out:Ljava/io/PrintStream;
3: new #14; //class java/util/Date
6: dup
7: dup
8: invokespecial #15; //Method java/util/Date."<init>":()V
11: dup
12: invokevirtual #19; //Method java/util/Date.getTime:()J
15: ldc2_w #20; //long 18000000l
18: ladd
19: invokevirtual #25; //Method java/util/Date.setTime:(J)V
22: invokevirtual #38; //Method java/io/PrintStream.println:(Ljava/lang/Object;)V
Full implementation of the time machine also requires interception of java.util.Calendar.getInstance. However, since it's basically similar as java.util.Date constructor case, I don't go thru it here.
Controlling which classes to instrument
In the beginning I was preaching about selectivity. Now it's time to approve my words.
My Java agent, like any other Java agent, takes one parameter. My agent uses this single string to define one or more regular expressions, which define whether a class is instrumented (match) or not (no match). So, right now the javaagent-switch is defined like this:
-javaagent:c:\temp\tm.jar=paci/.*
This results in instrumentation of classes which are in a package hierarchy starting with "paci". Here's our test program:
package paci;
import java.util.Date;
import org.joda.time.DateTime;
public class TimemachineTest {
public static void main(String[]
args) {
System.out.println( new Date( ) );
System.out.println( new DateTime( ) );
}
}
Wed Jun 20 21:45:12 EEST 2012
2012-06-20T16:45:12.279+03:00
Plain java.util.Date call works, but Joda Time (org.joda.time.DateTime) doesn't. Let's modify our instrumentation targets to include also Joda Time packages. The javaagent-switch now looks like this:
-javaagent:c:\temp\tm.jar=paci/.*:org/joda/.*
VĂ³ila, all of a sudden we start getting correct results with Joda Time, too. This is what gets printed out
Wed Jun 20 21:55:34 EEST 2012
2012-06-20T21:55:34.417+03:00
What if you have the same library in multiple applications, but only want to instrument some of them? The answer is to use java.security.ProtectionDomain parameter of transform method. From protection domain you can get the code source and its location. All you have to do is to parse the URL and decide whether to instrument or not. Here's a sample code source URL printed out by my agent:
file:/C:/programs/joda-time-2.1-dist/joda-time-2.1/joda-time-2.1.jar
But it breaks my application server!
Oh yeah, that's what happened to me. Since WebSphere Application Server or actually some of its components use ASM for its own purposes, it's possible to blow out the server with version mismatch. This in turn means classloader hacking. It's doable and not hard to implement, but it's another story.
Full source of the agent can be found here
Okie dokie, we are set with our time machine exercise.
No comments:
Post a Comment