Wednesday, December 17, 2008

The Maxine Inspector: First Contact

Once everything is up and running, things are ready to become really interesting. This article is about the Maxine Inspector, the powerful debugging tool that makes the entire Maxine project - in my opinion - one of the most interesting VM research projects these days.

The Inspector supports the close observation of an application running on Maxine, including the VM itself. This goes as far as being able to inspect the values currently stored in processor registers. All code that was compiled from Java source code, i.e., also the Maxine sources, can be inspected in native code and Java bytecode representation. Access to the source code representation is currently not supported.

I've only just started exploring the things that are possible with the Inspector. The Java application I've been using is the Hello World application known from my previous article; the one that comes with Maxine. As a side note, I just discovered today that it is not necessary to use the command line bin/max vm -cp VM/bin util.HelloWorld to run Hello World; apparently, the max script is capable of nicely doing that for us:

~/workspace/maxine~maxine$ bin/max helloworld

Anyway. On to the really interesting things.

Exploring the Inspector

Running an application in the Inspector is as simple as invoking it via the max script, only using the inspect command instead of vm, which was used to simply run the application:

~/workspace/maxine~maxine$ bin/max inspect -cp VM/bin util.HelloWorld

It takes a while and dumps lots of information on the screen, but eventually the (large) window shown in the image below appears. It really makes you wish you had two extra-large screens with exorbitant resolution available.

From left to right, and top to bottom, the Inspector GUI has the following elements:
  • Threads: all currently existing threads in the Maxine instance being observed.
  • Registers: the processor register values at this moment in time in the current thread.
  • Stacks: all threads' call stacks, with all the methods that are on the stacks right now.
  • Methods: the (native or byte) code of selected Java methods.
  • Breakpoints: a list of breakpoints that can be activated and deactivated.
  • Thread Locals: thread-local state.
I obviously don't know about the details of all of them (yet).

At this point in time, the Inspector shows the Maxine VM at the earliest possible point at which it can be observed: at the entry of the first Java method that is ever entered once the boot image has been loaded into memory and the boot image loader has jumped into the image. The Methods window contains a tab named MaxineVM.run[0]; this indicates that the method at whose start the Inspector is right now is Maxine.run(). Below the tab, the entire signature of the method is given: int run(Pointer, Pointer, Pointer, Word, Word, int, Pointer)[0].

Of course, the first success for an Inspector novice like me is to successfully run the inspected application as if nothing special was going on. This can be achieved by clicking the Resume button (which looks like the small image to the right) in the Methods window. I had to click it multiple times until the menu bar turned red, indicating termination of the VM. After closing the Inspector and looking at the console again, I discovered the Hello World! message somewhere in between the lots of output generated by the Inspector.

Inspecting Hello World

Let's do something real this time. After starting the Inspector again (see above), I want it to run all the VM initialisation code and come back to me once HelloWorld.main() is entered. Since I want to see both the machine code and the Java bytecodes of the method, I need to activate the bytecode view. This is done by clicking the black triangle icon left to the run() method's signature in the Methods window and selecting the Display Bytecodes option. Now, the two representations will appear side by side. The triangle icon opens context menus in all Inspector windows.

Next, I set a breakpoint at the beginning of HelloWorld.main(). I do this by opening the context menu for the Breakpoints window and selecting Break at Method Entry->Method on classpath, by name....

A dialog box appears. In the text input field at its top, I enter the name of the class (HelloWorld) and select the correct one from the list that appears; it is util.HelloWorld. Once the selection is confirmed by double-clicking the class name or clicking the Select button, another dialog box appears that lists all the available methods. Here, I select main(). Once that is done, the breakpoint is registered and activated and appears in the Breakpoints window.

The Inspector can now be told to continue execution until it reaches the breakpoint by (in my case, twice) clicking the Resume button. After the second click, some window borders flash red, and the Inspector window finally comes to a halt, looking a bit more full than before. Several things are interesting about the new scenario.

Threads

This window now shows four different threads, one of which (no. 2 with VM ID 0) is the main thread. Another is the primordial thread, which is, upon closer observation, the only thread that was running when the Inspector window appeared for the first time. The two remaining threads are for the garbage collector and Maxine's JIT compiler.

Registers

There are now four different tabs in the Registers window, one for each currently existing thread. The register set for the main thread is being shown, which makes sense as the breakpoint we have reached pertains to this thread. Clicking on the other tabs reveals the different register values for the other threads and also updates the Stacks and Thread Local windows as the thread being observed changes.

Stacks

The current call stack is a lot more full than at startup time, and we can see that HelloWorld.main(), the top method, has been invoked reflectively through Method.invoke(). Clicking on the latter will update several of the windows accordingly, but that is out of scope now. We can also see that the invocation of HelloWorld.main() was eventually reached from an activation of VmThread.run(), which was apparently invoked from a native method that cannot be inspected (just click on its name and you'll see). In fact, the methods from VmThread.run() to JavaRunScheme.lookupAndInvokeMain() are all Maxine methods, which can be seen from the tool tips that appear when the mouse pointer hovers over their names a while: their fully qualified names are given on the far right of the tool tip.

Thread Locals

This window now also has four tabs, one for each thread, and the values of the thread-local data can be inspected.

Methods

Finally, we come to the most interesting window. It has also one more tab, namely for the method HelloWorld.main(). It is immediately apparent that the Inspector has reached a breakpoint: it is highlighted by a yellow box. The red arrow indicates the current instruction pointer, and it can be seen which machine code instructions correspond to what bytecode instruction. Apparently, the method prologue has already been executed, and the first instruction corresponding to an actual Java bytecode instruction in main() is about to be executed.

Now, I want to see what a String object looks like; and I know I can easily get hold of it once it has been put on the stack by the LDC bytecode instruction (the second in main()). So I click on the INVOKEVIRTUAL instruction (note how the machine code view is adjusted) and then on the Run to selected instruction (ignoring breakpoints) button above the bytecode instructions (see right for an image). (I had to select-and-click twice to reach the INVOKEVIRTUAL instruction.)

Looking at the machine code generated for the LDC bytecode instruction, it seems the pointer to the String object was stored in the RDI register of my Pentium 4 CPU. Letting the mouse pointer hover over the green rdi text will yield a tool tip showing that there is indeed a String there.

And this is when we get to one of the coolest features of the Maxine Inspector. It is truly capable of (in the Smalltalk sense) inspecting objects. Clicking on the green rdi text (or on the green value stored in the RDI register as visible in the Registers window) yields a window displaying various details on the String object we're after.

Here are some of the details:
  • Object layout. The numbers at the left-hand side of the window indicate at which offset from the object pointer the corresponding values are stored. From this window, we can learn that the first word in the object is a pointer to another object representing the inspected object's class. (These are called hubs in Maxine, and clicking on the green text will yield an inspector for the hub, which displays, amongst other things, the entries of the class's virtual method table. But that's another story. For now, just remember that everything green indicates an object that can be inspected.) The second word in the String seems to be related to synchronisation, the third points to an array of characters representing the String, and so forth.
  • Slot names. It is incredibly useful to be able to reason about object contents in terms of the names the values are stored with, instead of using slot numbers and offsets.
  • Slot contents. The values of all of the object's slots are visible. As mentioned above, clicking on the green entries will open another inspector (again, green means object).
The context menu of the inspector (black triangle) contains several options for controlling what is displayed, and how. For example, it is possible to show the concrete heaps in which the different values are stored.

Done

This shall be it for now. I am totally baffled with the things one can do with the Inspector, and I wish I had had something like this much earlier. During my doctoral research, I was also working with a JVM implemented in Java (Jikes), and did not have any such support. The question remains whether it is possible to port at least some of the Maxine Inspector functionality over to Jikes, to make this great research VM as debuggable as Maxine.

Monday, December 15, 2008

Setting Up a Development Environment for Maxine in Eclipse

To be honest, I prefer Eclipse over NetBeans. This is mostly because of being used to using it; I won't start any religious wars on this topic. So, when I discovered that the Maxine Mercurial repository also contains Eclipse .project files, I decided to switch. It meant a bit of work, but here is another tutorial on how to set up a development environment for Maxine - this time in Eclipse, and using the correct steps for building. Those were pointed out by Doug Simon, whom I thank for his help!

First of all, Eclipse is not officially available for Solaris. That doesn't seem to be a major problem; Java's platform independence promises seem to be justified in this regard. A build of Eclipse is available; the ZIP file just needs to be uncompressed, it contains a complete Eclipse installation.

Once Eclipse is installed, some suggestions by the Maxine developers should be followed. For instance, I use these settings in eclipse/eclipse.ini:

-vm /usr/jdk/latest
-vmargs
-XX:MaxPermSize=512m
-Xmx1g
-Xms512m
-Duser.name=Michael Haupt

It is also recommended to install some plugins that are useful for development. One of them was not installable for me, though; nevertheless, the workspace runs fine, the problem does not seem to be vital. One plugin that is not mentioned by the Maxine developers is the one for Mercurial; it can be installed by following the instructions given on the plugin home page.

Maxine Sources: Getting Them Right

One thing I learned during the past few days is that the maxine~netbeans-inspector project in the Mercurial repository is currently not active. Eventually, it will yield a new implementation of the Inspector as a bunch of NetBeans plugins instead of the Swing application that exists now. So, this project is not of any further interest for now, especially given this article is about Eclipse.

The two repositories to be cloned are now these:

https://kenai.com/hg/maxine~maxine
https://kenai.com/hg/maxine~extras

Note that the maxine~extras repository is empty. It may contain interesting things in the future, though.

For each of them, the following steps need to be performed.
  1. From the menu, select File->Import....
  2. In the Mercurial category, choose the Clone Repository using Mercurial option and click Next >.
  3. Enter the repository URL in the URL field and tick the Search for .project files in clone and use them to create projects. checkbox. Finally, click Finish.
Two things are odd about this setting, at least for me:
  • The clone dialog window does not close after finishing.
  • Existing projects are not recognised.
I believe this is due to the status of the Mercurial plugin.

To make the various Maxine projects available in the Eclipse workspace, the following needs to be done for the two subdirectories named maxine~maxine and maxine~extras (once it contains projects) in the Eclipse workspace directory.
  1. From the menu, select File->Import....
  2. In the General category, choose Existing Projects into Workspace.
  3. Click the Browse... button next to the Select root directory: text box and select the workspace/maxine~maxine directory.
  4. A list of importable projects appears. For me, the list contains the projects Assembler, Base, Inspector, JDWP, Native, Tele, TeleJDWP, VM, and VMDI. Do not tick the Copy projects into workspace check box; they are already there. Click Finish.
Once this is done, Eclipse will build the projects in the workspace. Do not bother about this; there is an external tool responsible for doing it right. By the way, for me, the console output window in Eclipse turns black once the Native project is built. This is funny but not problematic.

Building Maxine

Before I get to building, I need to mention libproc.h once more - it is still important to get this file and put it in the right place in the sources. The location has changed since my last posting; the file is now to be placed in the Native/tele/solaris directory.

Here's another thing I learned from Doug Simon. There is a convenient wrapper script for all things Maxine; it resides in maxine~maxine/bin and is called max. For the script to be able to work, the JUNIT4_CP environment variable must be set. This has to point to the JAR file containing the JUnit 4 classes. I set this variable in my ~/.bashrc as follows, boldly exploiting the fact that Eclipse comes with a JUnit 4 plugin:

export JUNIT4_CP=/export/home/haupt/eclipse/plugins/org.junit4_4.3.1/junit.jar

Running the max script without any arguments gives an informative help message detailing all the options that can be given. For now, the two commands that are of interest are build and image. In fact, building a complete Maxine VM and boot image is as easy as invoking the max script twice from within the maxine~maxine directory:

~/workspace/maxine~maxine$ bin/max build
~/workspace/maxine~maxine$ bin/max image

It is safe to ignore the warnings.

Running Hello World

Guess what: the max script can also be used to run Maxine! Running the Hello World application coming with the VM is now trivial:

~/workspace/maxine~maxine$ bin/max vm -cp VM/bin util.HelloWorld

That's all it needs.

Tuesday, December 09, 2008

Maxine

Maxine is an exciting virtual machine research project conducted at Sun Labs. The Maxine VM is a metacircular Java VM, i.e., it is implemented (almost) completely in the Java programming language. As all of the high-level abstractions available in Java can be used to implement the VM, the code is rather well accessible, albeit still very complex, given the complexity of the domain.

The Maxine source code is available under the GPL 2.0. The code is very well organised and modular: all of the VM's features are encapsulated behind well-defined interfaces, whose concrete implementations are chosen at VM build time. This allows for circumventing the need to employ a preprocessor: the code thus behaves very well in an IDE.

Its metacircularity aside, which alone does not set Maxine apart from other JVM implementations like IBM's Jikes RVM, Maxine comes with a tool called the Maxine Inspector that makes it truly unique. The Inspector is actually a debugger, but an extremely sophisticated one. It allows for analysing every single aspect of the running VM in a most comfortable way. For instance, double clicking on the value stored in a processor register will, provided the register contains a valid object pointer, yield an object inspector (much in the fashion of Smalltalk) providing all kinds of information on that particular instance. It is also possible to trace back JIT-compiled machine code instructions to the corresponding byte code and even application or JVM source code.

In a nutshell, this is how I'd like to do VM debugging and development.

There are some things about Maxine that might be called downsides, but it's actually not that bad. First and foremost, Maxine requires powerful hardware. Maxine is, at the moment, restricted to 64-bit platforms. To make development and debugging comfortable, more than one 64-bit core and some 4 GB of memory are required. As for the operating system, Solaris is the one on which things are most smooth; Mac OS X is quite OK, and Linux is extremely problematic. (One might consider this a valid reason to start using Solaris.)

I intend to do some work with Maxine, and would like to share what I learn. Today, I'd like to start with a description of how I made it compile and run for me.

Hardware, Operating System, and Software

Happily, my employer equipped me with a dual-core 64-bit machine with 4 GB RAM and enough hard disk space to allow for a coexistence of Linux, Windows, and Solaris. I chose to work on Maxine on Solaris for two reasons: it's the recommended platform for Maxine, and I'm curious.

Thanks to a very helpful and capable student sysadmin (thanks, Tobias!), making the three operating systems coexist was possible in almost no time. Installing most of the required software on Solaris is also easy: a 64-bit Java and the NetBeans IDE can simply be installed using the Solaris package manager.

Mercurial is the source code management system to use. It is available for separate download and can be installed like this (provided pfexec is configured correctly):

bzip2 -d SUNWmercurial-0.9.5-i386.pkg.bz2
pfexec pkgadd -d SUNWmercurial-0.9.5-i386.pkg

The instructions below are based on those available from the Maxine home page, and have been adapted to fit the use of Mercurial. Moreover, some steps I had to do to make things work in my own environment are mentioned.

Getting the Maxine Sources

The Maxine Mercurial repository is hosted at http://kenai.com/projects/maxine. More particularly, the three different repository URLs are as follows:

https://kenai.com/hg/maxine~maxine
https://kenai.com/hg/maxine~extras
https://kenai.com/hg/maxine~netbeans-inspector

Importing the sources—and the NetBeans projects contained therein—in NetBeans is simple. For each of the above locations, the following steps need to be taken:
  1. Select Versioning -> Mercurial -> Clone Other...
  2. Enter the corresponding URL and click "Next >".
  3. Confirm the push and pull paths by clicking "Next >" once more.
  4. Leave the "Scan for Netbeans Projects after Clone" checkbox ticked, and confirm the destination directory by clicking "Finish".
Once this has been done for all three locations (note that maxine~extras appears to be empty), NetBeans will show a list of projects and some warnings about reference problems due to projects that could not be found. This can easily be mended by right-clicking on each of the problematic projects, choosing "Resolve Reference Problems..." from the context menu, and assigning the correct projects accordingly.

Compiling MaxineNative

The MaxineNative project represents the boot image loader implemented in C, which cannot be compiled from within NetBeans right now.

To make this compile, a single header file from the OpenSolaris code base is required; it is libproc.h. This file needs to be placed in the Native/inspector/solaris directory. (Be careful to use the right link; I managed to download a HTML representation of the file linked-to from the Maxine home page, which obviously would not compile. Quite embarrassing. The link given here is the one to the unformatted C source code.)

It is important to use the right C compiler to compile MaxineNative. By default, OpenSolaris (which is the distribution I'm using) installs the GNU C compiler, which cannot be used. To get the ANSI C compiler, the sunstudioexpress package can be installed—that's probably a bit too much as it also contains Fortran and C++ compilers, but this is what I did.

Another important step is to adjust the PATH environment variable to make sure the ANSI C compiler is executed instead of the GNU C compiler.

export PATH=/usr/bin:/usr/gnu/bin:/usr/X11/bin:/usr/sbin:/sbin

By default, /usr/gnu/bin comes before /usr/bin, which leads to GCC taking priority over CC, which in turn leads to interesting error messages when compiling MaxineNative.

Running Hello World

Of course, Maxine comes with a Hello World application. Once the VM is properly built according to the instructions from the home page and above, the binary can be found in Native/generated/solaris/maxvm; the directory could be added to the PATH. The Hello World application is located in VM/test/util/HelloWorld.java, which can simply be compiled from the command line using javac.

From the VM/test directory, the application can be run like this:

../../Native/generated/solaris/maxvm -cp . util.HelloWorld