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
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.
This section gives an insight into debugging the DRL virtual machine version 1.0 and provides tips on resolving non-standard debugging issues.
This section gives instructions on different scenarios of debugging the VM source code.
For ordinary tests, start the ij executable with the debugger enabled, as follows:
vm\build\vm.sln
. ij
executable. Select the Debugging tab and specify
the command and command arguments. zlib1.dll
to the location of the VM executable.
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
run <your_params>
To attach to the running VM process, do the following:
gdb –p <PID of ij>If the VM crashed during execution, use the core dump to analyze the crash:
gdb ij core
This section includes some tips on optimizing the debug process and getting more debug information.
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.
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:
ret
instruction. F11
to go a single instruction.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.
gdb
command print and specify the stack_dump
or
st_print
in the interpreter or the JIT compiler mode respectively.
_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.
__asm {int 3}
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.
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.
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:
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
/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.
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)
.
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.
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()
.
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
.
The following flags control tracing compilation of a method:
DBG_TRACE_SUMM
prints a short summary about the compiled
method: the compilation status (success or failure), the name, signature, bytecode
size, the start and end addresses of the generated code, and the compilation ID
(the sequential number of the method compiled by Jitrino.JET). DBG_DUMP_BBS
dumps the bytecode and marks up basic
blocks boundaries. DBG_TRACE_CG
prints information about each compiled
bytecode instruction: the state of the Java* operand stack before
the instruction, the known state of local variables at the given point, and the
native code generated for the instruction. The order instructions appear in the
log file is the depth-first order, the same as when processing instructions during
compilation. See the file trace.cpp, function toStr2()
for the legend
of the operand stack items print-out. DBG_TRACE_LAYOUT
prints the results of the code layout,
mostly, the address ranges for the basic blocks. DBG_DUMP_CODE
dumps generated code for the given method,
the method’s actual addresses, intermixed with appropriate bytecode instructions.
Note
For DBG_DUMP_CODE
and DBG_TRACE_CG
, Jitrino.JET can print
disassembled code in addition to raw hexadecimal dumps. For that, the compiler requires
an external disassembler. Currently, Jitrino.JET is configured to use the external
library lwdis.dll/liblwdis.so
that must be located in the same directory
as jitrino.dll/libjitrino.so
. The name lwdis stands for light weight
disassembler. The library must export the function disasm()
. Refer
to the file trace.cpp, the DISFUNC
definition for details on calling
convention and signature.
DBG_CHECK_STACK
prints nothing but instruments code
to check stack integrity. The methods compiled by Jitrino.JET use EBP-based frames.
These frames can mask errors, for example, wrong convention usage with a VM helper
call. Using this option performs run-time checks and INT3 raised in case the stack
integrity broken. DBG_BRK
inserts INT3 at the method entry point, so
it can be stopped in the debugger.
Note
An instance variable of class Compiler
, dbg_break_pc
can
be used to insert INT3
at the specified program counter of a method.
The following flags trace run-time life of the method:
DBG_TRACE_EE
prints entering: <method-name>
and exiting: <method-name>
at the method entrance and exit points.DBG_TRACE_BC
traces execution of every bytecode instruction
by printing a string of the following format before executing the instruction:
<method-name> @ PC=<pc>
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
.
DBG_TRACE_RT
traces run-time support calls, for example,
getting address of ‘this’, support for root set enumeration and stack
unwinding.Note
The output goes to jet.log, with the address (both native and PC) where the event happens, and some other info.
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.
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
JIT_compile_method_with_params
method. There you can find locals with methodName
, methodTypeName
(Class
name) and methodSig
(Signature). -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.
-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.
-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.
–XX:jit.arg.codegen.verify=2
Existing checks:
RegAllocCheck
– check that there are no conflict register assignments Levels:
RegAllocCheck
and Liveness
verification
after SpillGen
, Location
check after emitter. (Default in debug build) 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.
void Jitrino::Ia32::RegAlloc2::Opand::update():
Assertion `p\spans[i].beg != 0' failed.
void Jitrino::Ia32::Inst::assignOpcodeGroup(Jitrino::Ia32::IRManager*):
Assertion `opcodeGroup' failed.
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).
* Other brands and names are the property of their respective owners.