A generated native executable is heavily optimized code with minimal symbol information which makes debugging harder. This can be solved by embedding debug information into the resulting binary at build time. This information tells the debugger precisely how to interpret the machine code and point it back to the original Java method.
In this guide you will learn how to debug a native executable using the standard Linux GNU Debugger (GDB).
Note: Native Image debugging with GDB currently works on Linux with initial support for macOS. The feature is experimental.
To build a native executable with debug information, provide the -g
command-line option for javac
when compiling the application, and then to the native-image
builder.
This enables source-level debugging, and the debugger (GDB) then correlates machine instructions with specific source lines in Java files.
Follow the steps to test debugging a native executable with GDB. The below workflow is known to work on Linux with GDB 10.1.
bash <(curl -sL https://get.graalvm.org/jdk)
Save the following code to the file named GDBDemo.java.
public class GDBDemo {
static long fieldUsed = 1000;
public static void main(String[] args) {
if (args.length > 0) {
int n = -1;
try {
n = Integer.parseInt(args[0]);
} catch (NumberFormatException ex) {
System.out.println(args[0] + " is not a number!");
}
if (n < 0) {
System.out.println(args[0] + " is negative.");
}
double f = factorial(n);
System.out.println(n + "! = " + f);
}
if (false)
neverCalledMethod();
StringBuilder text = new StringBuilder();
text.append("Hello World from GraalVM Native Image and GDB in Java.\n");
System.out.println(text.toString());
}
static void neverCalledMethod() {
System.out.println("This method is unreachable and will not be included in the native executable.");
}
static double factorial(int n) {
if (n == 0) {
return 1;
}
if (n >= fieldUsed) {
return Double.POSITIVE_INFINITY;
}
double f = 1;
while (n > 1) {
f *= n--;
}
return f;
}
}
Compile it and generate a native executable with debug information:
$JAVA_HOME/bin/javac -g GDBDemo.java
native-image -g -O0 GDBDemo
The -g
option instructs native-image
to generate debug information. The resulting native executable will contain debug records in a format GDB understands.
Notice that you can also pass -O0
which specifies that no compiler optimizations should be performed. Disabling all optimizations is not required, but in general it makes the debugging experience better.
Launch the debugger and run your native executable:
gdb ./gdbdemo
The gdb
prompt will open.
Set a breakpoint: type breakpoint <java method>
to set a breakpoint and run <arg>
to run the native executable. You can put breakpoints configured by file and line, or by method name. See below the example of a debugging session.
$ gdb ./gdbdemo
GNU gdb (GDB) 10.2
Copyright (C) 2021 Free Software Foundation, Inc.
...
Reading symbols from ./gdbdemo...
Reading symbols from /dev/gdbdemo.debug...
(gdb) info func ::main
All functions matching regular expression "::main":
File GDBDemo.java:
5: void GDBDemo::main(java.lang.String[]*);
(gdb) b ::factorial
Breakpoint 1 at 0x2d000: file GDBDemo.java, line 32.
(gdb) run 42
Starting program: /dev/gdbdemo 42
Thread 1 "gdbdemo" hit Breakpoint 1, GDBDemo::factorial (n=42) at GDBDemo.java:32
32 if (n == 0) {
(gdb) info args
n = 42
(gdb) step
35 if (n >= fieldUsed) {
(gdb) next
38 double f = 1;
(gdb) next
39 while (n > 1) {
(gdb) info locals
f = 1
(gdb) ...
In case your native executable segfaults, you can print the backtrace of the entire stack (bt
).
The debugger points machine instructions back from the binary to specific source lines in Java files. Note that single stepping within a compiled method includes file and line number information for inlined code. GDB may switch files even though you are still in the same compiled method.
Most of the regular debugging actions are supported by GDB, namely:
The generation of debug information is implemented by modeling the Java program as an equivalent C++ program. Since GDB was primarily designed for debugging C (and C++), there are certain considerations to be taken into account when debugging Java applications. Read more about Native Image debugging support in the reference documentation.