Apache Harmony is retired at the Apache Software Foundation since Nov 16, 2011.

The information on these pages may be out of date, or may refer to resources that have moved or have been made read-only.
For more information please refer to the Apache Attic

Debugging DRL Virtual Machine and JIT Compiler

Debugging the DRL Virtual Machine and the JIT Compiler

  1. About This Document
  2. Debugging the Virtual Machine
    1. How to Debug the VM
      1. Basic Debugging Steps
      2. Attaching the Debugger to the Live Process
    2. How to Get More of Your Debugging
      1. Configuration of Tracing
      2. Native Stack Examination
      3. Java* Stack Examination
      4. Setting Smart Breakpoints
      5. Handling Java* Threads as Native Threads
      6. Debugging Deadlocks with the CriticalSection Synchronization Primitive
      7. Getting a Class Name for an Object
  3. Debugging the Jitrino.JET Baseline Compiler
    1. How to Enable Tracing in Jitrino.JET
    2. How to Configure Trace Logging
      1. Group 1: Compilation Results Tracing
      2. Group 2: Run-time Execution Tracing
    3. How to Get More of Your Tracing
      1. Tracing Example
  4. Debugging the Jitrinio.OPT Optimizing Compiler

About This Document

This document gives practical instructions on how to debug the DRL virtual machine and its baseline just-in-time compiler Jitrino.JET. For a definition of components and details on their internal structure, consult the DRL Virtual Machine Developer’s Guide supplied with the DRLVM image.

The document includes two groups of debugging tips, one for VM tips, and the other for JIT compiler tips, as shown below.

Back to top

Debugging the Virtual Machine

This section gives an insight into debugging the DRL virtual machine version 1.0 and provides tips on resolving non-standard debugging issues.

Back to top

How to Debug the VM

This section gives instructions on different scenarios of debugging the VM source code.

Basic Debugging Steps

For ordinary tests, start the ij executable with the debugger enabled, as follows:

On Windows*
  1. Start Microsoft Visual Studio* and open the solution file vm\build\vm.sln.
  2. Open the source code that you need to debug, set breakpoints and perform all other preliminary steps.
  3. Select the vmcore project and open its properties to specify the location of the ij executable. Select the Debugging tab and specify the command and command arguments.
  4. Copy the ZLib tool library zlib1.dll to the location of the VM executable.
  5. Start debugging via the menu Debug > Start, click NO in the popup dialog offering to build the project.
On Linux*
  1. Set up the LD_LIBRARY_PATH to point to the deploy/jre/bin directory. Change the working directory to the location of the VM executable and run:
    gdb ij
    
  2. Set breakpoints and perform all other preliminary steps.
  3. On the command line, specify debugging parameters by typing:
    run <your_params>
    

Attaching the Debugger to the Live Process

To attach to the running VM process, do the following:

On Windows*
  1. Start Visual Studio* .
  2. Go Debug > Processes.
  3. Select the VM process and click Attach.
    If you built the VM in the debug mode, the Microsoft Debug Runtime window appears. Click the Debug button to go to the crash point.
On Linux*
Run:
gdb –p <PID of ij>
If the VM crashed during execution, use the core dump to analyze the crash:
gdb ij core

Back to top

How to Get More from Your Debugging

This section includes some tips on optimizing the debug process and getting more debug information.

Configuration of Tracing

Consult the Getting Started guide delivered with DRLVM bundle for information on VM standard and non-standard configuration options. Tracing-related options might be useful for debugging purposes.

Native Stack Examination

The debugger might draw the stack incorrectly when the JIT or native stubs are involved. To avoid this, set esp as the memory location and 4-byte integer values as the output format. As a result, you can examine the stack word by word and look into the code for each number 0x00… by using the Disassembly window.

When the VM has crashed

A very specific case on Windows*: the VM has crashed and you only see the stack frame in the call stack 0x00000000. This means that the program has jumped or called to a null pointer.

You might still get the stack trace. For that, do the following:

  1. Open the Disassembly window.
  2. Find any ret instruction with no parameters in the memory space.
    Open any source file, open the context menu, and select Go to Disassembly to find any ret instruction.
  3. When found, select the ret instruction and click Set Next Statement in the context menu.
  4. Type F11 to go a single instruction.
    You may see several instructions near the current point of execution. If you see a call instruction, you have found an appropriate call site and you can see the call stack in the Call Stack window. If you have found a wrong call site, repeat the instruction from step 2.

Java* Stack Examination

On Windows*
Running the VM in the interpreter mode, you can get Java* methods stack trace almost at any point of execution. For that, break the execution of the VM and select the Interpreter frame in the Visual Studio* Call Stack window. Then, in the watch window, add the watch stack_dump(). The stack dump appears in VM output window. Running the VM in the JIT mode, use the st_print() function for the same purpose.
On Linux*
Use the gdb command print and specify the stack_dump or st_print in the interpreter or the JIT compiler mode respectively.

Setting Smart Breakpoints

On Windows* / IA-32
Place breakpoints in source code by inserting calls to the _CrtDbgBreak() function. Placing the call inside a condition calls the function only for the specified case you need to debug. Analogously, you can use the Windows* API function DebugBreak() or print INT 3.

Note

This requires recompiling the VM.

On Linux*
Run the following:
__asm {int 3}

Handling Java* Threads as Native Threads

DRL VM has 1:1 mapping between native threads visible in the debugger and Java* threads.
To work with Java* threads individually, freeze the threads you do not need with the help of the debugger, and continue execution of other threads.

Debugging Deadlocks with the CriticalSection Synchronization Primitive

The CriticalSection primitive is a common cause of deadlocks. If the code has stopped at a critical section, you can try to find the thread that owns this primitive.

On Windows*
The file WinNT.H located in <PlatformSDK>\Include contains the definition for the structure _RTL_CRITICAL_SECTION, which contains the description of the CriticalSection primitive. You can get the owning thread for the CriticalSection primitive in a number of ways, as indicated below.

Lookup in Memory

While debugging the code in Visual Studio, do the following:

  1. Go Debug > Windows > Memory 1.
  2. In the address line, enter the address of the critical section.
  3. Open the context menu and select 4-byte Integers. This sets the fourth DWORD from the beginning of the critical section as the owning thread ID in hexadecimal representation.

Note

Visual Studio* displays thread IDs in decimal representation.

In the Watch Window

In the watch window of Visual Studio*, insert the following:

* ((int* )<cs-ptr>+3)

Where <cs-ptr> is the address of your critical section

On Linux*
The file /usr/include/bits/pthreadtypes.h contains the description of the pthread_mutex_t type. To get the ID of the thread owning the CriticalSection primitive, in gdb execute:
x/4w <address of your mutex primitive>

The third word in the output contains the owning thread descriptor you are looking for.

Getting a Class Name for an Object

You can often need to find out the class name for a Java* object used in VM code. For example, you may need to get the class name for an object of the type ManagedObject * (which is a direct pointer to the heap). For that, insert the following expression into the watch window in Visual Studio on Windows* or print the command of gdb on Linux* :

((VTable*)(*((int*)obj)))->clss->name->bytes

Variables of the types jobject and Object_Handle are references to ManagedObject * types. These structures contain a single element, a pointer to ManagedObject * type object. To use the expression above, de-reference the variable, for example, substituting obj in the expression above with a cast to (ManagedObject *)(*(int *)obj).

Back to top

Debugging the Jitrino.JET Baseline Compiler

To use debugging and tracing in Jitrino.JET, use the debug build or the release build with the JET_PROTO macro defined. See the file jdefs.h for a definition of the available flags.

Back to top

How to Enable Tracing in Jitrino.JET

Currently, Jitrino.JET provides no interface or command line to control tracing options. To enable tracing, set the compile_flags variable at the entry point to the method Compiler::compile(). At that point, the global variable Compiler::g_methodsSeen contains the ID of the method being compiled, and the instance variable m_fname contains its fully qualified name with no signature. Obtain these options through a call to Compiler::m_infoBlock.get_flags().

Back to top

How to Configure Trace Logging

Tracing flags control compilation results output and trace run-time execution, as described below. Tracing results are created in each run in the directory where the ij executable starts: compilation results are in jet.log, and run-time output is in jet.rt.log.

Group 1: Compilation Results Tracing

The following flags control tracing compilation of a method:

Group 2: Run-time Execution Tracing

The following flags trace run-time life of the method:

Notes

The output string for DBG_TRACE_EE and DBG_TRACE_BC uses a specific format: before the string, an estimated call depth is printed and the string gets indentation based on the call depth. After the string, the string ID is printed. The estimated call depth may help to identify where a method was called from. The string ID can be helpful for setting a conditional breakpoint in the debugger for a complex scenario. For that, set a condition for the static variable cnt in the function rt_dbg, file trace.cpp.
Turning on the option DBG_TRACE_BC may slow down execution extremely and may result to a gigantic file jet.rt.log.

Note

The output goes to jet.log, with the address (both native and PC) where the event happens, and some other info.

Back to top

How to Get More from Your Tracing

To identify one or more problematic methods with another stable JIT compiler, use the execution manager. With this technique, some methods are compiled by the stable JIT, and the rest goes to the JIT being debugged. With a simple binary search, you can find the problematic method rather quickly.

Note

Try turning off parallel compilation when using this technique (refer to VM’s -Xno_parallel_jit option).

To get details in case of a crash with no adequate stack trace or IP location available, turn on the option DBG_TRACE_EE to see, in which method the crash happens. As the second step, turn on DBG_TRACE_BC for this particular method to find the exact bytecode instruction. Often, this cuts the code to analyze down to 5-10 native instructions.

To set a breakpoint and stop execution at a specific point, use trace.cpp:rt_dbg to break execution at the specified bytecode instruction or at the entry point of the specified method.

Tracing Example

This is an example of code that turns on various tracing scenarios. The code must be placed in the method Compiler::compile().

#if defined(_DEBUG) || defined(JET_PROTO)

// Turns on a short summary of all methods

compile_flags |= DBG_TRACE_SUMM;

// A handy constant

static const unsigned TRACE_CG = DBG_DUMP_BBS | DBG_TRACE_CG |

DBG_TRACE_LAYOUT | DBG_TRACE_SUMM |

DBG_DUMP_CODE;

// For methods in the range (1000;15000), print out the complete code generator dumps

if (g_methodsSeen>1000 && g_methodsSeen<15000) {

compile_flags |= TRACE_CG;

}

// For methods getSomeValue() and for all methods in class MyClass,

// trace enter and exit

if (NULL != strstr(m_fname, "::getSomeValue") ||

NULL != strstr(m_fname, "MyClass::") ) {

compile_flags |= DBG_TRACE_EE;

}

// For the method crashes_some_times() in class MyClass trace every

// bytecode execution: the last bytecode in the log is the most probable

// cause of the failure

if (!strcmp(m_fname, "MyClass::crashes_some_times")) {

compile_flags |= DBG_TRACE_EE|DBG_TRACE_BC;

}

// Break into debugger (INT3) at the entry of the stop_at_entry() method

if (!strcmp(m_fname, "MyClass::stop_at_entry")) {

compile_flags |= DBG_BRK;

}

// Break into debugger (INT3) inside the method

if (!strcmp(m_fname, "MyClass::stop_somewhere_in_the_middle")) {

dbg_break_pc = 50;

}

// Trace run-time support calls: unwind, getting the address of 'this', root

// set enumeration

if (!strcmp(m_fname, "MyClass::something_wrong_with_unwind_here")) {

compile_flags |= DBG_TRACE_RT;

}

// By-pass run-time tracing for java/* classes

if (m_fname == strstr(m_fname, "java/")) {

compile_flags &= ~(DBG_TRACE_EE| DBG_TRACE_BC);

}

#endif

Back to top

Debugging the Jitrino.OPT Compiler

You get a crash in Jitrino.OPT code. How to identify the method being compiled?
One of the possible ways: go up the call stack to the frame of JIT_compile_method_with_params method. There you can find locals with methodName, methodTypeName (Class name) and methodSig (Signature).
How to create a compilation log for a particular method?
Use the following:
 -XX:jit.any_id.filter=class_name_prefix.method_name_prefix –XX:jit.any_id.arg.log=ct,irdump 

Note that the last IR Dumps in the log include addresses for all emitted instructions. 0xdeadbeef address means that the instruction was not emitted.

You get a crash somewhere in managed code. How to determine where the execution stopped?
Rerun with the following:
 -XX:jit.arg.log=info 

This will generate the file ./log/info.log that will contain lines similar to:

 <24 CS_OPT. start java/lang/ThreadGroup.remove(Ljava/lang/Thread;)V byte code size=86
	   24> CS_OPT. end native code size=444 code range=[0xa5dca390,0xa5dca54c] 

This way, you can find the interval (code range) containing the address of your crash. If there is no such range, your crash is not in managed code.

How to stop at the beginning of the particular method that was compiled by Jitrino.OPT?
Use the following:
-XX:jit.any_id.filter=class_name_prefix.method_name_prefix
–XX:jit.any_id.arg.codegen.break=on

"int 3" instruction will be generated as the first one in the method.

How to enable some more compile-time debug checks in Jitrino.OPT?
Use the following:
 –XX:jit.arg.codegen.verify=2 

Existing checks:

  • RegAllocCheck – check that there are no conflict register assignments
  • Liveness verification - check that all operand uses have defs
  • Location check – check that all operands has assigned physical location.

Levels:

  • 0 – no verification (Default in release build)
  • 1 – RegAllocCheck and Liveness verification after SpillGen, Location check after emitter. (Default in debug build)
  • 2 – the same as for level 1 + RegAllocCheck after RegAlloc2 (bp_regalloc) + Liveness verification after each pass

Thus, if you discover Liveness verification failure after SpillGen, rerun the test with verify=2, as the problem might occur at earlier stages.

What does such an assertion in RegAlloc mean?
void Jitrino::Ia32::RegAlloc2::Opand::update(): 
Assertion `p\spans[i].beg != 0' failed.
It is not related to the register allocation itself. This means that an operand in the code may be used at a point where it is yet undefined. This is a liveness problem (see the previous question).
What does such an assertion mean? (Instruction cannot be found in encoding table)
void Jitrino::Ia32::Inst::assignOpcodeGroup(Jitrino::Ia32::IRManager*): 
Assertion `opcodeGroup' failed.
You may encounter other similar assertions. This means that the code is trying to create an instruction that can never be emitted because it contradicts with the architecture specification.

The encoding table in the encoder (enc_table.cpp) contains instruction definitions with mnemonic and operands of exact sizes. The assertion fires when the instruction you tried to create cannot be found in the table. Make sure the instruction operands have proper sizes and possible physical locations (described by constrains attached to each operand).

Back to top

* Other brands and names are the property of their respective owners.