We illustrate how to debug the native image of a Java application, produced by MicroDoc GraalVM Embedded, and running on a remote embedded device. The debugging experience is illustrated both inside the IntelliJ IDEA and VSCode IDE. The embedded device runs Linux on an ARMv8-A processor (running in AArch64 mode), like a Raspberry Pi 4 or 5.

Building Java native images using MicroDoc GraalVM Embedded with Maven and Gradle

⏩ TL;DR (too long; didn’t read)

  1. Read documentation of Native Image on https://www.graalvm.org/latest/reference-manual/native-image/#build-a-native-executable
  2. Configure native-image options to enable cross compilation.

🏗️ Build a native image on the build computer

In order to build a native image for a target device, we need to specify some options to native-image. For example:

$ <somewhere>/graalvm/bin/native-image \
    -O0 -g -cp <somewhere>/bin -o <somewhere>/hello-world --target=linux-aarch64 \
    --native-compiler-path=<somewhere>/aarch64-none-linux-gnu-gcc \
    --native-linker-options=-L<somewhere>/lib \
    -H:+UnlockExperimentalVMOptions \
    -H:+UseCAPCache -H:CAPCacheDir=<somewhere>/cap-cache/aarch64-linux-gnu \
    -H:-UnlockExperimentalVMOptions \
    com.example.HelloWorld

This builds the executable <somewhere>/hello-world that runs on the target device.

⚠️ When building debug native images of your application, consider using the following options: -H:-StripDebugInfo and -H:DebugInfoSourceSearchPath=<somewhere>/src

Building with Maven

We recommend using the official Maven plugin for GraalVM Native Image building. Please follow the instructions on the official documentation and specify the following options in the profile section:

<profile>
    <id>native-aarch64</id>
    <build>
        <plugins>
            <plugin>
                <groupId>org.graalvm.buildtools</groupId>
                <artifactId>native-maven-plugin</artifactId>
                <version>${native.maven.plugin.version}</version>
                <extensions>true</extensions>
                <executions>
                    <execution>
                        <id>build-native</id>
                        <goals>
                            <goal>build</goal>
                        </goals>
                        <phase>package</phase>
                    </execution>
                </executions>
                <configuration>
                    <fallback>false</fallback>
                    <agent>
                        <enabled>true</enabled>
                    </agent>
                    <buildArgs>
                        <buildArg>--target=linux-aarch64</buildArg>
                        <buildArg>--native-compiler-path=$somewhere/aarch64-none-linux-gnu-gcc</buildArg>
                        <buildArg>--native-linker-options=-L$somewhere/lib</buildArg>
                        <buildArg>-H:+UnlockExperimentalVMOptions</buildArg>
                        <buildArg>-H:+UseCAPCache</buildArg>
                        <buildArg>-H:CAPCacheDir=$somewhere/cap-cache/aarch64-linux-gnu</buildArg>
                        <buildArg>-H:-UnlockExperimentalVMOptions</buildArg>
                    </buildArgs>
                </configuration>
            </plugin>
        </plugins>
    </build>
</profile>

In orde to build the target image, you need to specify the profile when ubuilding with Maven:

mvn -Pnative-aarch64 package

Building with gradle

We recommend using the official Gradle plugin for GraalVM Native Image building. Please follow the instructions on the official documentation and specify the following options in the graalvmNative section:

graalvmNative {
    buildArgs.add('--target=linux-aarch64')
    buildArgs.add("--native-compiler-path=$somewhere/aarch64-none-linux-gnu-gcc")
    buildArgs.add("--native-linker-options=-L$somewhere/lib")
    buildArgs.add('-H:+UnlockExperimentalVMOptions')
    buildArgs.add('-H:+UseCAPCache')
    buildArgs.add('-H:CAPCacheDir=$somewhere/cap-cache/aarch64-linux-gnu')
    buildArgs.add('-H:-UnlockExperimentalVMOptions')
    buildArgs.add("-Dllvm.bin.dir=$System.env.BUNDLE_D0EPS/llvm/bin")
}

In order to build the target image, you need to specify the nativeBuild when building with Gradle:

gradle nativeBuild