Trial Division Factorization Disassembly
When Experimenting with Async Profiler, I created a basic trial division factorization Java application. To run it, download the OpenJDK build if it isn’t already installed:
mkdir -p ~/java/binaries/jdk/x64
cd ~/java/binaries/jdk/x64
wget https://aka.ms/download-jdk/microsoft-jdk-17.0.7-linux-x64.tar.gz
tar xzf microsoft-jdk-17.0.7-linux-x64.tar.gz
Test the factorization application to verify that the Java build works.
export JAVA_HOME=~/java/binaries/jdk/x64/jdk-17.0.7+7
cd ~/repos/scratchpad/demos/java/FindPrimes
$JAVA_HOME/bin/javac Factorize.java
$JAVA_HOME/bin/java Factorize 123890571352112309857
# Use 4 threads to speed things up
$JAVA_HOME/bin/java Factorize 123890571352112309857 CUSTOM_THREAD_COUNT_VIA_THREAD_CLASS 4
Using hsdis
hsdis is a HotSpot plugin for disassembling dynamically generated code. Chriswhocodes was kind enough to build hsdis for various platforms and share the binaries on his website – hsdis HotSpot Disassembly Plugin Downloads (chriswhocodes.com). Download the appropriate hsdis binary and move it to the OpenJDK build’s lib directory, e.g.
wget https://chriswhocodes.com/hsdis/hsdis-amd64.so
export JAVA_HOME=~/java/binaries/jdk/x64/jdk-17.0.7+7
mv hsdis-amd64.so $JAVA_HOME/lib/
ls -l $JAVA_HOME/bin/hsdis*
We will need the PrintAssembly option to disassemble the code generated by the compiler when running a Java program. This option requires diagnostic VM options to be unlocked. This is the full command line for generating the disassembly from the application’s execution. The output is redirected to a code.asm file since it can be voluminous.
$JAVA_HOME/bin/java -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly Factorize 123890571352112309857 CUSTOM_THREAD_COUNT_VIA_THREAD_CLASS 4 > code.asm
Here is a snippet of the disassembly in code.asm:
============================= C1-compiled nmethod ==============================
----------------------------------- Assembly -----------------------------------
Compiled method (c1) 2052 266 2 java.math.BigInteger::implMulAdd (81 bytes)
total in heap [0x00007f2e5943ca90,0x00007f2e5943d038] = 1448
relocation [0x00007f2e5943cbf0,0x00007f2e5943cc28] = 56
main code [0x00007f2e5943cc40,0x00007f2e5943ce00] = 448
stub code [0x00007f2e5943ce00,0x00007f2e5943ce30] = 48
metadata [0x00007f2e5943ce30,0x00007f2e5943ce38] = 8
scopes data [0x00007f2e5943ce38,0x00007f2e5943cee0] = 168
scopes pcs [0x00007f2e5943cee0,0x00007f2e5943d010] = 304
dependencies [0x00007f2e5943d010,0x00007f2e5943d018] = 8
nul chk table [0x00007f2e5943d018,0x00007f2e5943d038] = 32
--------------------------------------------------------------------------------
[Constant Pool (empty)]
--------------------------------------------------------------------------------
[Verified Entry Point]
# {method} {0x00000008000a47c0} 'implMulAdd' '([I[IIII)I' in 'java/math/BigInteger'
# parm0: rsi:rsi = '[I'
# parm1: rdx:rdx = '[I'
# parm2: rcx = int
# parm3: r8 = int
# parm4: r9 = int
# [sp+0x50] (sp of caller)
0x00007f2e5943cc40: mov %eax,-0x14000(%rsp)
0x00007f2e5943cc47: push %rbp
0x00007f2e5943cc48: sub $0x40,%rsp
0x00007f2e5943cc4c: movabs $0x7f2e38075370,%rax
0x00007f2e5943cc56: mov 0x8(%rax),%edi
0x00007f2e5943cc59: add $0x2,%edi
0x00007f2e5943cc5c: mov %edi,0x8(%rax)
0x00007f2e5943cc5f: and $0xffe,%edi
0x00007f2e5943cc65: cmp $0x0,%edi
0x00007f2e5943cc68: je 0x00007f2e5943cd52 ;*iload {reexecute=0 rethrow=0 return_oop=0}
; - java.math.BigInteger::implMulAdd@0 (line 3197)
0x00007f2e5943cc6e: movslq %r9d,%r9
0x00007f2e5943cc71: movabs $0xffffffff,%rax
0x00007f2e5943cc7b: and %rax,%r9
...
Finding the Java Installation Path
In the above example, I have used a Java build in a custom path. If you are using a Java build that is already installed, then a few extra steps might be needed to determine where the JAVA_HOME path, e.g.
saint@ubuntuvm:~$ which java
/usr/bin/java
saint@ubuntuvm:~$ ls -l `which java`
saint@ubuntuvm:~$ ls -l /etc/alternatives/java
Leave a Reply