Quick Links:

Releases | Mailing Lists | Source Control | Issue Tracker | Regression Tests

5 Debugging Jikes RVM

Chapter 5
Debugging Jikes RVM

This page contains some debugging hints for Jikes RVM. It is assumed that you are familiar with debugging techniques. If you aren’t, it is advisable to read a book about the subject.

5.1 General debugging tips

5.1.1 Strange NullPointerExceptions

Jikes RVM converts every segmentation fault to a NullPointerException. If a NPE occurs at a point where it really shouldn’t be able to, it’s likely that the real cause is a segmentation fault.

5.1.2 Assertions

All debugging should be done with assertion-enabled builds if possible. You can also try using ExtremeAssertion builds.

5.1.3 Options

The Jikes RVM and MMTk provide several options to print out debugging information.

If you’re debugging a problem in the optimizing compiler, you can also print out the IR.

You can also use the options to change the behaviour in various ways (e.g. switch off certain optimizations) if you have a suspicion about the causes of the problem.

5.1.4 Debugger Thread

Jikes has an interactive debugger that you can invoke by sending SIGQUIT to Jikes while it’s running:

pkill -SIGQUIT JikesRVM

In previous versions of Jikes, that stopped all threads and provided an interactive prompt, but currently it just dumps the state of the VM and continues immediately (that’s a known issue: RVM-570). Debug fields in classes

Several classes in the code base provide static boolean fields like DEBUG or VERBOSE which can be set to get more debugging information.

5.1.5 Shutdown hooks

You can write custom shutdown hooks to dump gathered information when the VM terminates. Note that shutdown hooks won’t be run if the VM is terminated via a signal (see RVM-555)

Do not use the ExitMonitor from the Callbacks class because it’s less reliable.

5.1.6 Tests

The test coverage is poor at the moment. Nevertheless, if you’re very lucky, one of the smaller test cases will fail. See Testing Jikes RVM for details on how to run the tests and define your own.

5.2 Tools

There are different tools for debugging Jikes RVM:

5.2.1 GDB

There is a limited amount of C code used to start Jikes RVM. The rvm script will start Jikes RVM using GDB (the GNU debugger) if the first argument is -gdb. Break points can be set in the C code, variables, registers can be expected in the C code.

rvm -gdb <RVM args> <name of Java application> <application args>

The dynamically created Java code doesn’t provide GDB with the necessary symbol information for debugging. As some of the Java code is created in the boot image, it is possible to find the location of some Java methods and to break upon them. To build with debug symbols, you’ll need to set the appropriate property as described in Building Jikes RVM.

Details of how to manually walk the stack in GDB can be found here.

5.2.2 rdb

rdb is a debugger that was developed specifically for Jikes RVM. It allows you to inspect the bootimage. If you’re running Mac OS, you can also use it to debug a running Jikes RVM.

5.2.3 Other Tools

Other tools, such as valgrind, are occasionally useful in debugging or understanding the behaviour of JikesRVM. The rvm script facilitates using these tools with the ’-wrap’ argument.

rvm -wrap "<wrapper-script-and-args>" <rest of command line>

For example, cachegrind can be invoked by

rvm -wrap "/path/to/valgrind --tool=cachegrind" <java-command-line>

The command and arguments immediately after the -wrap argument will be inserted into the script on the command line that invokes the boot image runner. One useful variant is

rvm -wrap echo <rest of command line>

5.3 Debugging Optimizing Compiler Problems

To debug problems in the optimizing compiler, use a configuration whose bootimage is compiled with the baseline compiler and which contains the AOS (prototype-opt, BaseAdaptive*). Faster configurations (such as development) have the drawback of a longer bootimage compilation time which won’t be amortized unless the problem occurs late.

It is advisable to use -X:vm:errorsFatal=true when debugging optimizing compiler problems. This will prevent the optimizing compiler from reverting to the baseline compiler for certain kinds of errors.

It is strongly recommended to run with advice file generation (see Experimental Guidelines). The produced advice files can then be used to try to reproduce the bug. If this step is successful, the advice files should be minimized to determine the set of methods that cause the failures. This can be done automatically (e.g. via delta debugging) or by hand.

You can also switch on paranoid IR verification in IR.java. Note that this is not well tested at the moment because we don’t run any regression tests with it. Use a BaseAdaptive* configuration if you switch this on (bootimage builds with the optimizing compiler and paranoid IR fail at the time of this writing).

5.3.1 Deadlocks

To debug a deadlock, run the VM under a time limit and send SIGQUIT (to force a thread dump) a few seconds before killing the VM. On Linux, you can use the timelimit program (should be available in the repositories for Debian-based distributions).

5.3.2 Excluding Garbage Collection problems

The garbage collectors that are included with the Jikes RVM are generally stable. Therefore, if you see a problem that does not occur during the collection itself, it is likely not a garbage collection problem. You can exclude problems related to garbage collection by building with other collectors. For example, you can choose a collector that doesn’t move objects (e.g. MarkSweep) or a collector that doesn’t require write barriers (e.g. Immix instead of GenImmix).

5.4 Debugging problems related to the class library

The classes in the libraryInterface directory overwrite those in the class library. This allows replacement of classes for debugging purposes. For example, if you wanted to debug a problem with the class java.example.Foo from the class library, you could simply copy its source file to the appropriate path of the libraryInterface (e.g. libraryInterface/GNUClasspath/LGPL/src/java/example/Foo.java). You can then adapt the source in the library interface to your needs.

5.5 GDB Stack Walking

Sometimes it is desirable to examine the state of the Java stack whilst using GDB to step instructions, break on addresses or watch particular variables. These instructions are based on an email sent by Martin Hirzel to the rvm-devel list around 15th September 2003. The instructions have been updated by Laurence Hellyer to deal with native threading and renamed RVM classes.

1) To learn about the stack frame layout on IA32, look at rvm/src/org/jikesrvm/ia32/StackframeLayoutConstants.java

Currently (2009/10/23) the layout is:

+4: return address 
fp -> 0: callers fp 
-4: method id 
(remember stack grows from high to low)

2) To learn how to get the current frame pointer and other context information, look at the genPrologue() method in rvm/src/org/jikesrvm/compilers/baseline/ia32/BaselineCompilerImpl.java. It first retrieves the thread register (esi on IA32), which points to an instance of RVMThread, and then retrieve fields from that instance.

3) To find the offset of field RVMThread.framePointer, add the following lines to the end of BootImageWriter.main(String[]):

   // added to get framePointer offset from RVMThread to manually walk stacks in GDB 
   say("offsetofRVMThread.framePointer==" + ArchEntrypoints.framePointerField.getOffset());

Do a build to print this info. On my config I got +148, but this can change between versions

4) To get started, let’s examine an example stack that contains methods whose code is in the boot image. We pick one that is likely to be invoked even in a simple hello-world program. In my RVM.map, 0x351eae9c is the address of org.jikesrvm.mm.mmtk.ReferenceProcessor.growReferenceTable();

5) Setting a break point on this address

(gdb) break *0x351eae9c 
Breakpoint 2 at 0x351eae9c

And run the program to the break point

Breakpoint 2, 0x351eae9c in ?? ()

Step some instructions into the method and then dump the registers

(gdb) stepi 30 
0x351eaf03 in ?? () 
(gdb) info registers 
eax           0x200  512 
ecx           0x0    0 
edx           0x0    0 
ebx           0x7431  29745 
esp           0x420e1934      0x420e1934 
ebp           0xb0206ed0      0xb0206ed0 
esi           0x4100758c      1090549132 
edi           0x19c54105556 
eip           0x351eaf03      0x351eaf03 
eflags        0x202  514 
cs            0x17    23 
ss            0x1f    31 
ds            0x1f    31 
es            0x1f    31 
fs            0x1f    31 
gs            0x37    55

The current FP is stored in RVMThread.framePointer which we found out in 3) is at offset +148. ESI points to the current RVMThread object so we can access the FP value like so:

(gdb) x ($esi+148) 
0x41007620:    0x420e1954

Note that the FP is at a higher address than ESP which is what we would expect

The return address is at FP+4 so to get the return address we can do:

(gdb) x (*($esi+148))+4 
0x420e1958:    0x351eadde

We can look in RVM.map for the largest method address smaller than 0x351eadde which is org.jikesrvm.mm.mmtk.ReferenceProcessor.addCandidate(java.lang.ref.Reference, org.vmmagic.unboxed.ObjectReference). Examining ReferenceProcessor.java we find that this is the only method that calls growReferenceTable so this is correct

Get the return address from the next frame

(gdb) x *(*($esi+148))+4 
0x420e1980:    0x352ebd1e

Which corresponds to org.jikesrvm.mm.mmtk.ReferenceProcessor.addSoftCandidate(java.lang.ref.SoftReference, org.vmmagic.unboxed.ObjectReference) which is a caller of addCandidate.

We can follow the stack back up to the top where we will read a FP of 0 (look in rvm/src/org/jikesrvm/ia32/StackframeLayoutConstants.java for details)