Summary
This third post is the final follow-up on the series of posts
investigating how to overload GDB’s start
and run
commands to
automatically debug aarch64 binaries using Qemu’s user-space emulation
mode. In Part 2, I overloaded the start
command and just redirected the run
command to basically perform a start
, leaving the user to finish it
with the continue
command.
Run
The final stretch in completing the commands was to also make run
work. While at it, it this post combines the previous start.py
and
~/.gdbinit
files into one file (now just ~/.gdbinit
), using an
approach detailed on stackoverflow.
To actually get run
to work, the largest hurdle is preventing GDB
from crashing, which would happen if a continue
is added. For
whatever reason, this extra continue does NOT cause GDB to crash if
it is in a start
command (meaning the user typed start
or run
).
The crash seams to be related to a GDB check that thinks it is on an
x86-64 system, when in fact it has just connected to the Qemu emulator
running aarch64 code. To resolve this, I have Python raise an exception,
which stop GDB from continuing the rest of its run
code. The program
has already run, and the only output that is printed is a short Python
message. This can be redirected when starting GDB to /dev/null
if it
is particularly bothersome, although it is possible that that would also
redirect other important messages.
The Python-defined start
command is able to tell if it is being called
by the user or by the hook-run
preface to the run
command by
checking for a temporary file called .tmp.gdb.isRun
, in the current
directory. If this file exists, then the user first called run
, which
determined the binary type was aarch64. Otherwise, the user just called
start
directly.
The Code
The below ~/.gdbinit
file contains everything needed to overload run
and start
to work with Qemu:
### --------------------------------------------------------------------
### .gdbinit
### Author: William Ughetta
###
### Overloads `start` and `run` commands to debug aarch64 binaries
### with Qemu user-space emulation on port 1234.
###
### Helpful resources:
### https://gist.github.com/nojhan/c3bc28e2fa0608f21551
### https://stackoverflow.com/a/17960363
### https://stackoverflow.com/a/12452235
### https://stackoverflow.com/a/15032459
### --------------------------------------------------------------------
define hook-run
get_aarch64
if ($aarch64)
shell touch .tmp.gdb.isRun
start
end
end
define get_aarch64
# Enable Logging
shell rm -f gdb.log
set logging file gdb.log
set logging overwrite on
set logging redirect on
set pagination off
set logging on
# Get the target and architecture.
info target
# Disable Logging
set logging off
set pagination on
set logging redirect off
set logging overwrite off
# i.e. `/path/to/a.out', file type elf64-littleaarch64.
shell echo -n 'set $aarch64 = 0' > gdb.aarch64
shell if [[ -n $(grep "file type elf64-.*aarch64." gdb.log) ]]; \
then echo 1 >> gdb.aarch64; fi
source gdb.aarch64
shell rm -f gdb.aarch64 gdb.log
end
python
### --------------------------------------------------------------------
### start.py
### Author: William Ughetta
### Overrides GDB start command to enable seamless aarch64 debugging.
### --------------------------------------------------------------------
import os.path
import re
import subprocess
class StartCommand(gdb.Command):
"""
Overides the default GDB start command to enable cross-platform
aarch64 debugging. Note that the "run" command must NOT be changed.
Instead, "hook-run" should be defined to call this command if the
platform is aarch64.
"""
def __init__ (self):
super(StartCommand, self).__init__("start",
gdb.COMMAND_RUNNING, gdb.COMPLETE_NONE)
def invoke (self, args, from_tty):
infoTarget = gdb.execute("info target", from_tty, True)
target = re.search(r"/.*', file type", infoTarget)
if not target:
print("No symbol table loaded. Use the \"file\" command.")
return
target = target.group(0)[:-12]
aarch64 = re.search(r"file type elf64-.*aarch64.", infoTarget)
if (aarch64):
try:
gdb.execute("kill", from_tty, True)
except:
pass
subprocess.call("kill -9 $(ps -u | grep -m 1 " +
"'qemu-aarch64 -g 1234' | " +
"awk '{print $2}') 2>/dev/null", shell=True)
gdb.execute("file " + target, from_tty, True)
print("Starting Qemu on Port 1234: " + target + " " + args)
subprocess.call("qemu-aarch64 -g 1234 " + target + " " +
args + " &>/dev/stdout </dev/stdin &", shell=True)
gdb.execute("target remote :1234", from_tty, True)
# Execute the `run` command or the `start` command
isRun = os.path.isfile(".tmp.gdb.isRun")
if (isRun): # `run`
gdb.execute("continue", from_tty, True)
subprocess.call("rm -f " + ".tmp.gdb.isRun", shell=True)
raise Exception("[Inferior 1 (Remote target) exited " +
"normally]")
else: # `start`
gdb.execute("break main", from_tty, True)
gdb.execute("continue", from_tty, True)
gdb.execute("clear main", from_tty, True)
else:
gdb.execute("break main", from_tty, True)
gdb.execute("run " + args, from_tty)
gdb.execute("clear main", from_tty, True)
StartCommand()
end
Sample Execution
The following execution examples were started with a hello.s
program
in a.out
(compiled with aarch64-linux-gnu-gcc -static -g hello.s
)
and run with gdb-multiarch a.out
.
The start
command with an aarch64 binary:
(gdb) start
Starting Qemu on Port 1234: /home/vagrant/workspace/a.out
0x00000000004003f0 in _start ()
Breakpoint 1, main () at hello.s:6
6 stp x29, x30, [sp, #-16]!
(gdb) c
Continuing.
hello, world
[Inferior 1 (Remote target) exited normally]
(gdb)
The run
command with an aarch64 binary:
(gdb) run
Starting Qemu on Port 1234: /home/vagrant/workspace/a.out
0x00000000004003f0 in _start ()
hello, world
[Inferior 1 (Remote target) exited normally]
Python Exception <class 'Exception'> [Inferior 1 (Remote target) exited normally]:
Error occurred in Python command: [Inferior 1 (Remote target) exited normally]
(gdb)
Mix of two start
’s and on run
command on aarch64:
(gdb) start
Starting Qemu on Port 1234: /home/vagrant/workspace/a.out
0x00000000004003f0 in _start ()
Breakpoint 7, main () at hello.s:6
6 stp x29, x30, [sp, #-16]!
(gdb) n
8 adr x0, cMes
(gdb)
9 bl printf
(gdb)
hello, world
11 mov w0, #0
(gdb) start
QEMU: Terminated via GDBstub
Starting Qemu on Port 1234: /home/vagrant/workspace/a.out
0x00000000004003f0 in _start ()
Breakpoint 8, main () at hello.s:6
6 stp x29, x30, [sp, #-16]!
(gdb) run
QEMU: Terminated via GDBstub
Starting Qemu on Port 1234: /home/vagrant/workspace/a.out
0x00000000004003f0 in _start ()
hello, world
[Inferior 1 (Remote target) exited normally]
Python Exception <class 'Exception'> [Inferior 1 (Remote target) exited normally]:
Error occurred in Python command: [Inferior 1 (Remote target) exited normally]
(gdb)
Note that while this also behaves normally with x86-64, the debugging
session should be quit before switching between binary types. Running
start
or run
on aarch64 and then x86-64 immediatley (or vice-versa)
will not work.