Disassembling Machine Code Generated by HotSpot
HotSpot generates lots of machine code on-the-fly (including the interpreter and the JITted code). This page describes how you can disassemble such code so you can understand how it is generated and what it does.
hsdis
First, you need an external shared library called hsdis
.
With the jdk/jdk repo (as of version 00ebc17f3cc6), you can build hsdis using the following on Linux/x64:
cd src/utils/hsdis mkdir build cd build wget http://ftp.gnu.org/gnu/binutils/binutils-2.19.1.tar.bz2 tar jxf binutils-2.19.1.tar.bz2 ln -s binutils-2.19.1 binutils cd .. make ARCH=amd64 CFLAGS="-w -fPIC" export LD_LIBRARY_PATH=$(pwd)/build/linux-amd64
This would generate hsdis-amd64.so
. Remember you need to include the path to this file in your
LD_LIBRARY_PATH
so HotSpot can pick it up.
For building hsdis
on other platforms, see here.
Disassembling the HotSpot Interpreter
The HotSpot interpreter is generated dynamically at VM start-up. Its contents can
depend on the command-line options (such as -XX:+TraceBytecodes
). Also, non-product JVMs will include lots of
debugging and assertion code in the interpreter, which may be distracting.
In general, if you’re just curious of how the interpreter handles a particular Java bytecode, your best bet is to use the product JVM.
For example, iconst_5
is implemented like this in templateTable_x86.cpp
void TemplateTable::iconst(int value) {
transition(vtos, itos);
if (value == 0) {
__ xorl(rax, rax);
} else {
__ movl(rax, value);
}
}
You can see the assembler listing with the following command. Note that most of
the code is just boiler-plate for setting up the interpreter states, and the actual bytecode
implementation is just a single mov
instruction.
java -XX:+UnlockDiagnosticVMOptions -XX:+PrintInterpreter -version [ .... snip .... ] iconst_5 8 iconst_5 [0x00007f09a066e180, 0x00007f09a066e1e0] 96 bytes 0x00007f09a066e180: push %rax 0x00007f09a066e181: jmpq 0x00007f09a066e1bf 0x00007f09a066e186: sub $0x8,%rsp 0x00007f09a066e18a: vmovss %xmm0,(%rsp) 0x00007f09a066e18f: jmpq 0x00007f09a066e1bf 0x00007f09a066e194: sub $0x10,%rsp 0x00007f09a066e198: vmovsd %xmm0,(%rsp) 0x00007f09a066e19d: jmpq 0x00007f09a066e1bf 0x00007f09a066e1a2: sub $0x10,%rsp 0x00007f09a066e1a6: mov %rax,(%rsp) 0x00007f09a066e1aa: mov $0x0,%r10 0x00007f09a066e1b4: mov %r10,0x8(%rsp) 0x00007f09a066e1b9: jmpq 0x00007f09a066e1bf 0x00007f09a066e1be: push %rax 0x00007f09a066e1bf: mov $0x5,%eax //store integer 5 to the top of stack 0x00007f09a066e1c4: movzbl 0x1(%r13),%ebx 0x00007f09a066e1c9: inc %r13 0x00007f09a066e1cc: mov $0x7f09be03c440,%r10 0x00007f09a066e1d6: jmpq *(%r10,%rbx,8) 0x00007f09a066e1da: nopw 0x0(%rax,%rax,1)
Up to JDK 11, the -XX:+PrintInterpreter
output is most uncommented so it’s hard to find
the C code that generates the assembler code. I am working on a patch
JDK-8204267 which generate extra comments to
show how the code is generated (the following output has been edited for clarity):
ifeq 153 ifeq [0x00007f830cc93da0, 0x00007f830cc941c0] 1056 bytes [ .... snip .... ] mov (%rsp),%eax add $0x8,%rsp test %eax,%eax ;;@FILE: /src/hotspot/cpu/x86/templateTable_x86.cpp jne 0x00007f830cc94177 ;; 2354: __ jcc(j_not(cc), not_taken); mov -0x18(%rbp),%rcx ;; 2120: __ get_method(rcx); // rcx holds method mov -0x28(%rbp),%rax ;; 2121: __ profile_taken_branch(rax, rbx); // rax holds updated MDP, rbx test %rax,%rax je 0x00007f830cc93dd8 mov 0x8(%rax),%rbx add $0x1,%rbx sbb $0x0,%rbx mov %rbx,0x8(%rax) add 0x10(%rax),%rax mov %rax,-0x28(%rbp) movswl 0x1(%r13),%edx ;; 2133: __ load_signed_short(rdx, at_bcp(1)); bswap %edx ;; 2135: __ bswapl(rdx); sar $0x10,%edx ;; 2138: __ sarl(rdx, 16); movslq %edx,%rdx ;; 2140: LP64_ONLY(__ movl2ptr(rdx, rdx)); add %rdx,%r13 ;; 2164: __ addptr(rbcp, rdx); test %edx,%edx ;; 2179: __ testl(rdx, rdx); // check if forward or backward branch jns 0x00007f830cc93eec ;; 2180: __ jcc(Assembler::positive, dispatch); // count only if backward branch mov 0x18(%rcx),%rax ;; 2184: __ movptr(rax, Address(rcx, Method::method_counters_offset())); test %rax,%rax ;; 2185: __ testptr(rax, rax); jne 0x00007f830cc93ead ;; 2186: __ jcc(Assembler::notZero, has_counters); push %rdx ;; 2187: __ push(rdx); push %rcx ;; 2188: __ push(rcx); callq 0x00007f830cc93e09 ;; 2189: __ call_VM(noreg, CAST_FROM_FN_PTR(address, InterpreterRuntime::build_method_counters),