Search code examples
assemblyjvmjvm-hotspot

How to see the machine code generated for JNI-Calls?


I want to see the machine code generated by the JVM to call a native method See here for details. I know the Option -XX:+PrintAssembly (Like here: https://stackoverflow.com/a/24718356/13912132) , but it only shows me the JIT-compiled code. I only want to see the short snippet, that sets up the registers/the stack, nothing else. The code to generate machine code for x86_64. Is there any commandline option that allows me to do that?


Solution

  • -XX:+PrintAssembly shows that, too. But there are nuances.

    Even though a native method does not have bytecodes, the activation of the method still requires JVM to do some work. Like with normal Java methods, this activation can be either interpreted or JIT compiled.

    A native method always has a special entry point for calling from interpreter. This entry is a part of the interpreter, and is shared between all regular native methods. Or, more precisely, there are two entry points: for synchronized methods and for non-synchronized, see TemplateInterpreterGenerator::generate_native_entry().

    To print these entry points, use -XX:+UnlockDiagnosticVMOptions -XX:+PrintInterpreter, then look for method entry point (kind = native) in the output:

    method entry point (kind = native)  [0x0000017e83dbefc0, 0x0000017e83dbfa60]  2720 bytes
    
    --------------------------------------------------------------------------------
    Argument 0 is unknown.RIP: 0x17e83dbefc0 Code size: 0x00000aa0
      0x0000017e83dbefc0:   mov     rcx,qword ptr [rbx+8h]
      0x0000017e83dbefc4:   movzx   ecx,word ptr [rcx+34h]
      0x0000017e83dbefc8:   pop     rax
      0x0000017e83dbefc9:   lea     r14,[rsp+rcx*8+0fffffffffffffff8h]
      0x0000017e83dbefce:   push    0h
      0x0000017e83dbefd3:   push    0h
      0x0000017e83dbefd8:   push    rax
      0x0000017e83dbefd9:   push    rbp
      0x0000017e83dbefda:   mov     rbp,rsp
      0x0000017e83dbefdd:   push    r13
      0x0000017e83dbefdf:   push    0h
      0x0000017e83dbefe4:   mov     r13,qword ptr [rbx+8h]
      ...
    

    If a native method is invoked many enough times, it gets JIT-compiled. I mean, the JVM generates a wrapper for calling the target native function from Java. Unlike the shared interpreter native entry, the wrapper is specialized for the particular native method.

    When -XX:+PrintCompilation is on, you'll see native wrappers marked by n symbol:

        667   18     n 0       java.lang.Thread::isAlive (native)   
    

    You'll also find these wrappers in -XX:+PrintAssembly output:

    java/lang/Thread.isAlive()Z  [0x00000238d2d62740, 0x00000238d2d62988]  584 bytes
    Argument 0 is unknown.RIP: 0x238d2d62740 Code size: 0x00000248
    [Entry Point]
      # {method} {0x00000238e5ebadd8} 'isAlive' '()Z' in 'java/lang/Thread'
      #           [sp+0x70]  (sp of caller)
      0x00000238d2d62740: mov     r10d,dword ptr [rdx+8h]
      0x00000238d2d62744: mov     r12,800000000h
      0x00000238d2d6274e: add     r10,r12
      0x00000238d2d62751: xor     r12,r12
      0x00000238d2d62754: cmp     rax,r10
      0x00000238d2d62757: je      238d2d62768h
      0x00000238d2d6275d: jmp     238cb2c7480h      ;   {runtime_call ic_miss_stub}
      0x00000238d2d62762: nop     word ptr [rax+rax+0h]
    [Verified Entry Point]
      0x00000238d2d62768: mov     dword ptr [rsp+0ffffffffffff9000h],eax
      0x00000238d2d6276f: push    rbp
      0x00000238d2d62770: mov     rbp,rsp
      0x00000238d2d62773: sub     rsp,60h
      0x00000238d2d62777: mov     qword ptr [rsp+20h],rdx
      0x00000238d2d6277c: cmp     rdx,0h
      0x00000238d2d62780: lea     rdx,[rsp+20h]
      0x00000238d2d62785: cmove   rdx,qword ptr [rsp+20h]  ; ImmutableOopMap{[32]=Oop }
      0x00000238d2d6278b: vzeroupper
      ...
    

    This code is generated by SharedRuntime::generate_native_wrapper.