The Inspector is the tool co-developed with the VM for debugging. Launching the Inspector is as simple as using the mx inspect command.

mx inspect -cp test.output.GCTest2

The Inspector window should appear in a few moments. Go here for other ways of launching the Inspector.

Testing Maxine


The most useful way to test Maxine is to execute some of the standard benchmarks on the image previously built with the mx image command. In this example, we will use the SpecJVM98 and DaCapo benchmarks. After downloading the benchmarks, set the following environment variables:

export SPECJVM98_ZIP=/Users/acme/benchmarks/
export DACAPOBACH_JAR=/Users/acme/benchmarks/dacapo-9.12-bach.jar

Then execute the following command:

mx test -insitu -tests=specjvm98,dacapobach
Running reference       SpecJVM98 _201_compress:               1607 ms
Running maxvm (std)     SpecJVM98 _201_compress:               3309 ms           2.059x
Running reference       DaCapo-bach avrora:                    3960 ms
Running maxvm (std)     DaCapo-bach avrora:                   13815 ms           3.488x

Note that the harness used to run the benchmarks against a reference VM is a little brittle. In particular, it compares the output written to stdout by the two executions and if they don’t match, it determines the Maxine VM execution to have failed. We’ve noticed that for some benchmarks on some platforms, the execution output is not such a reliable fingerprint. To see the actual output of the benchmark, and the exact command used to run it, look in the maxine-tester/insitu directory.

Regression testing

Maxine includes a set of tests that are useful in catching regressions and measuring progress when making changes. The three types of tests included in the distribution are described below.

JUnit tests

Tests the Maxine code base prior to building and running the VM. For example, there are JUnit tests for the general purpose utility classes in the Base project. There are also JUnit tests that use the various IR interpreters to ensure that each level of IR in Maxine’s compiler produces the correct output.

VM tests

These are tests that are executed on the VM. The first subcategory of VM tests are very simple unit tests that test a specific VM feature and/or Java bytecode instruction in isolation (i.e. avoiding as many other VM features as possible). To ensure strong isolation, these tests are built into the boot image and executed in such a way that precludes using a more general testing framework such as JUnit. The second subcategory of VM tests are called output tests. These tests are comprised of standard Java programs (i.e. they have a class containing a main method) that produce some deterministic output via System.out. These tests compare the output of these programs when run under Maxine VM and another trustworthy JVM (such as HotSpot).

Cross-ISA tests

When porting Maxine VM’s compilers to a new Instruction Set Architecture QEMU is utilized to virtually run unit tests and regress the correctness of the generated code. To be able to run cross-ISA tests Maxine VM relies on the gcc linaro toolchain and qemu. Assuming an Ubuntu 16.04 LTS installation, the following will install the required packages.

sudo apt-get install qemu-system-arm gcc-arm-none-eabi gdb-arm-none-eabi

Unfortunately for aarch64 the packages provided by ubuntu are not suitable. Qemu version is 2.5 while we need 2.10 and gcc-aarch64-linux-gnu although available in the Ubuntu repositories it does not include aarch64-linux-gnu-gdb, so we need to manually download both Qemu and the linaro toolchain.

For qemu:

bunzip2 qemu-2.10.1.tar.bz2
tar xvf qemu-2.10.1.tar
cd qemu-2.10.1
mkdir build
cd build
../configure --target-list=aarch64-linux-user,aarch64-softmmu
make -j
sudo make install

For gcc toolchain:

tar xf gcc-linaro-7.1.1-2017.08-x86_64_aarch64-linux-gnu.tar.xz
export PATH=$PATH:$(pwd)/gcc-linaro-7.1.1-2017.08-x86_64_aarch64-linux-gnu/bin


git clone
mkdir build
cd build
../configure --target-list=riscv32-softmmu,riscv64-softmmu,riscv32-linux-user,riscv64-linux-user --prefix=/opt/riscv
make -j
sudo make install

For the GCC toolchain please follow the instructions from

NOTE: When debugging RISC-V to make breakpoints work run the following in gdb

#set riscv use_compressed_breakpoint off

Logging and Tracing

Maxine provides two related mechanisms for logging and/or tracing the behavior of the VM, manual string-based logging using the com.sun.max.vm.Log class, or more automated, type-based logging, that is integrated with the Inspector, using com.sun.max.vm.log.VMLogger. These are related in that VMLogger includes string based logging as an option and so can replace the use of Log. Currently the VM uses a mixture of these two mechanisms, with conversion being done opportunistically. For simplicity, we will use the term tracing to describe string-based logging in the following. If you are adding logging to a VM component you are strongly encouraged to use the VMLogger approach.

Manual Tracing

Use the class com.sun.max.vm.Log to do manual tracing. The class includes a variety of methods for printing objects of various types. By default the output goes to the standard output but can be re-directed to a file by setting the environment variable MAXINE_LOG_FILE before running the VM. To selectively enable specific tracing in the VM, define a com.sun.max.vm.VMOption with the name -XX:+TraceXXX, where XXX identifies the tracing.

You should avoid string concatenation (or any other code involving allocation) in tracing code, especially inside a VmOperation. While this should not break the VM (allocation will fail fast with an error message if a VM operation does not allow it), allocation can add noise to your logs. Lastly, if the logging sequence involves more than one logging statement, you should bound the sequence with this pattern:

boolean lockDisabledSafepoints = Log.lock();
// multiple calls to Log.print...() methods

This will serialize logging performed by multiple threads. Of course, it will also serialize the execution of the VM and may well make the race you are trying to debug disappear!

Native Code Tracing

Maxine provides some tracing of the small amount of native code that supports the VM. By default this is conditionally compiled out of the VM image but can be selectively enabled by editing and rebuilding with mx build and rebuilding the VM image. This is particularly useful if the the VM crashes during startup. For example to enable all tracing set log-ALL to 1.

Type-based Logging

In type-based logging, the actual values that you want to log are passed to an instance of the com.sun.max.vm.log.VMLogger class using methods defined in the class. Evidently, at the VMLogger level, type-based logging is something of a misnomer, as it cannot know the types of the actual values. In practice the values are logged as untyped Word values, but extensive automated support is provided to handle the conversion to/from Word types. The optional tracing support is driven from the values in the log. For more details see Type-based Logging.

Debugging Maxine Java Tasks

The Maxine project includes a number of Java programs that can be launched as commands of the mx script. For example, the mx image command described above runs the com.sun.max.vm.prototype.BootImageGenerator class on a host JVM. This simplest way to debug such a command is to use the -d global option of the mx script. This will launch the Java program with extra options telling it to wait and listen for a JDWP-capable debugger on port 8000. You then configure a JDWP-enabled debugger to attach to this port.

The advantage of this approach is that you can easily launch the command with different command line arguments without having to create/modify an IDE launch configuration.

Core dump

To get a core dump from a Maxine VM process, it is simplest to do gcore <pid> from another shell. This forces a core dump but does not terminate the process, which continues after the dump is taken. An alternative is to use kill -s ABRT <pid> which does kill the process after the dump is taken. One other difference is that gcore allows the path to the core dump file to be specified with the -c <corefile> option, whereas kill puts it in a default location, typically core in the current working directory.

It is possible to force a core dump on a fatal VM error by setting the option -XX:+CoreOnError when running the VM.

The following invocation:

mx inspect --mode=attach --target=file --location=dumpfile

will then bring up the Inspector on the core dump. If you omit the --location argument, it will put up a dialog box.

Unfortunately this will only work if the associated Maxine VM was run with the -XX:+MakeInspectable option, otherwise some key data structures needed by the Inspector will not have been created.