How to Build the hsdis Disassembler Plugin on Ubuntu 18
May 23, 2019
3 minute read

hsdis is “A HotSpot plugin for disassembling dynamically generated code” as it is defined in OpenJDK/src/utils/hsdis/README. It is used particularly when you are working with other tools in OpenJDK that would like to show disassembled code, e.g. JMH, Java Microbenchmark Harness, or java’s -XX:+PrintAssembly.

I believe because it is platform dependent, it is not included together with the binary distributions, so you have to build it yourself.

Because this post is focused on Ubuntu 18 and particularly 18.04 because that is what I am using, I will use OpenJDK 11 since it is available in the Ubuntu package repository, as openjdk-11-jdk. Ubuntu actually switched to OpenJDK 11 as its default Java package recently.

  • Lets first create a container (you do not need to do this, but it isolates the process):
$ lxc launch ubuntu:18.04 hsdis
$ lxc exec hsdis -- su - ubuntu

Now lets update the package cache and install openjdk-11-jdk (I am using the headless version):

$ sudo apt update
$ sudo apt install openjdk-11-jdk-headless

Before downloading the source code, we need to enable the source repo, open /etc/apt/sources.list and uncomment these lines:

# deb-src http://archive.ubuntu.com/ubuntu bionic main restricted
# deb-src http://archive.ubuntu.com/ubuntu bionic-updates main restricted

Then update the cache again and install dpkg-dev package:

Note: dkpkg-dev package is actually not needed, but apt source gives error if you do not have it, however you still have the source package even if it gives the error. If you prefer not to install dpkg-dev package, then you need to install the build packages e.g. build-essentials.

$ sudo apt update
$ sudo apt install dpkg-dev

Now download the openjdk-11-jdk-headless source code:

$ apt source openjdk-11-jdk-headless
$ ls
openjdk-lts-11.0.3+7/
openjdk-lts_11.0.3+7-1ubuntu2~18.04.1.dsc
openjdk-lts_11.0.3+7-1ubuntu2~18.04.1.debian.tar.xz
openjdk-lts_11.0.3+7.orig.tar.xz

openjdk-lts-11.0.3+7 is the source code this package is built from. Now go to the hsdis source code:

$ cd openjdk-lts_11.0.3+7/src/utils/hsdis/
$ ls
Makefile  README  hsdis-demo.c  hsdis.c  hsdis.h

We need binutils to build hsdis. It is mentioned in the readme that binutils 2.17 or 2.19.1 is known to work, but I prefer using the latest one, so I will download:

$ wget http://ftpmirror.gnu.org/gnu/binutils/binutils-2.32.tar.gz
$ tar zxvf binutils-2.32.tar.gz

As it is described in the README, we need to export BINUTILS to point binutils source:

$ export BINUTILS=binutils-2.32

Since I am on an 64-bit platform, I will use the all64 target, you can use all if you are on 32-bit platform.

$ make all64

This will give an error about disassembler method call in hsdis.c, because the signature of this method is changed in recent binutils versions. It is not hard to fix, so go to hsdis.c line 316 and change this line:

  app_data->dfn = disassembler(native_bfd);

to this:

  app_data->dfn = disassembler(bfd_get_arch(native_bfd),
                               bfd_big_endian(native_bfd),
                               bfd_get_mach(native_bfd),
                               native_bfd);

Now we can retry (clean64 is a strange name for clean target but it is like this to delete 64-bit target folder):

$ make clean64 all64
$ ls build/linux-amd64/hs*
build/linux-amd64/hsdis-amd64.so

We successfully built the library and in order to use it, it has to be somewhere accessible by the jvm, and I find it simpler to put into the jvm/lib folder, so:

$ sudo cp build/linux-amd64/hsdis-amd64.so /usr/lib/jvm/java-11-openjdk-amd64/lib/

We can write a simple Hello World class and see its disassembled output:

$ cat Main.java
public class Main {
	public static void main(String[] args) {
		System.out.println("Hello World!");
	}
}
$ java -Xbatch -XX:+PrintCompilation -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly Main | more
     13    1    b  3       java.lang.Object::<init> (1 bytes)
Compiled method (c1)      13    1       3       java.lang.Object::<init> (1 bytes)
 total in heap  [0x00007fe4dcf0f010,0x00007fe4dcf0f3a0] = 912
 relocation     [0x00007fe4dcf0f188,0x00007fe4dcf0f1b0] = 40
 main code      [0x00007fe4dcf0f1c0,0x00007fe4dcf0f2a0] = 224
 stub code      [0x00007fe4dcf0f2a0,0x00007fe4dcf0f330] = 144
 metadata       [0x00007fe4dcf0f330,0x00007fe4dcf0f340] = 16
 scopes data    [0x00007fe4dcf0f340,0x00007fe4dcf0f358] = 24
 scopes pcs     [0x00007fe4dcf0f358,0x00007fe4dcf0f398] = 64
 dependencies   [0x00007fe4dcf0f398,0x00007fe4dcf0f3a0] = 8
Loaded disassembler from /usr/lib/jvm/java-11-openjdk-amd64/lib/hsdis-amd64.s
...

As you see the disassembler we just compiled is loaded and used.


The best way to receive updates is