Debugging arm64 programs is slightly easier natively on an arm64 server or a Raspberry Pi with a 64-bit operating system. However, it is often more convenient to write arm64 code using existing x86-64 machines. This post aims to make debugging arm64 code with GDB on x86-64 machines just as efficient as natively.
Qemu is a tool that is able to provide user-space emulation for arm64
(called “aarch64”) and many other architectures. Qemu is able to
connect to GDB through its remote server interface. This can be much
clunkier than GDB’s native start
command because either two
terminals are needed or Qemu needs to be run in the background, which
can make input and output more difficult.
To integrate Qemu into GDB, a command can be created to make running
a remote server just as easy as the native start
command. Let’s call
it qemu
(it will be invoked inside of GDB).
Define the qemu
command in GDB:
Create a ~/.gdbinit
file with the following contents (or just type
this into GDB):
define qemu
if $argc != 1
echo Usage: qemu a.out\n
else
shell kill -9 $(ps -u | grep -m 1 'qemu-aarch64 -g 1234' | awk '{print $2}') 2>/dev/null
file $arg0
shell qemu-aarch64 -g 1234 $arg0 &>/dev/stdout </dev/stdin &
target remote :1234
break main
continue
end
end
The qemu
command takes one argument only, which is the arm64 binary
that it will run.
First, the debug info is loaded with file $arg0
after attempting to
halt the previous debug session. arg0
is the first argument given to
qemu
. Next, GDB starts the remote Qemu user-space emulation with the
shell qemu-aarch64 -g 1234 $arg0 &>/dev/stdout </dev/stdin &
command. The -g 1234
specifies the default port 1234. This means
that there can only be one debug at a time because it only attempts to
use one port. The redirects allow Qemu to write to standard output and
read from standard input even while it is in the background. The only
restriction is this does not work with the GDB tui (i.e.
layout src
). It does work with Emacs and GDB in its normal mode.
Finally, GDB is told to connect to Qemu with target remote :1234
,
and it goes straight to main
.
Taking Command Line Parameters
The qemu
command can be extended to accept command line arguments
along with passing the input and output. While theoretically it could
be setup to take as many arguments as desired, the following command
accepts up to 7 additional command line arguments:
define qemu
if $argc <= 0 || $argc > 8
echo Usage: qemu a.out (7 arguments allowed maximum)\n
else
shell kill -9 $(ps -f -u $(whoami) | grep -m 1 'qemu-aarch64 -g 1234' | awk '{print $2}') 2>/dev/null
file $arg0
if $argc == 1
shell qemu-aarch64 -g 1234 $arg0 &>/dev/stdout </dev/stdin &
else
if $argc == 2
shell qemu-aarch64 -g 1234 $arg0 $arg1 &>/dev/stdout </dev/stdin &
else
if $argc == 3
shell qemu-aarch64 -g 1234 $arg0 $arg1 $arg2 &>/dev/stdout </dev/stdin &
else
if $argc == 4
shell qemu-aarch64 -g 1234 $arg0 $arg1 $arg2 $arg3 &>/dev/stdout </dev/stdin &
else
if $argc == 5
shell qemu-aarch64 -g 1234 $arg0 $arg1 $arg2 $arg3 $arg4 &>/dev/stdout </dev/stdin &
else
if $argc == 6
shell qemu-aarch64 -g 1234 $arg0 $arg1 $arg2 $arg3 $arg4 $arg5 &>/dev/stdout </dev/stdin &
else
if $argc == 7
shell qemu-aarch64 -g 1234 $arg0 $arg1 $arg2 $arg3 $arg4 $arg5 $arg6 &>/dev/stdout </dev/stdin &
else
shell qemu-aarch64 -g 1234 $arg0 $arg1 $arg2 $arg3 $arg4 $arg5 $arg6 $arg7 &>/dev/stdout </dev/stdin &
end
end
end
end
end
end
end
target remote :1234
break main
continue
end
end