Using Qemu within GDB Part 5

Changes

This is the fifth and final (Update: see Part 6!) version of a series of posts working with GDB to integrate Qemu into the start and run commands. While GDB does support remote debug restarting with set remote exec-file filename, this does not work for Qemu. (GDB remote documentation).

Part 4 presented a fully functioning set of start and run commands. To improve upon it, this post removes all use of temporary files, adds helpful error messages, improves the clarity of the ~/.gdbinit file (by defining a qemu command that hooks into start and run instead of redefining one of the latter two), and finally by improving Emacs integration to open the source window immediatley on start.

Version 5

  • Restarting
  • Emacs
  • Error messages
  • Normal x86-64 debugging
### --------------------------------------------------------------------
### .gdbinit
### Author: William Ughetta
### Overloads `start` and `run` commands to debug aarch64 binaries with
### Qemu user-space emulation on port 1234.
### --------------------------------------------------------------------

define hook-start
  set-aarch64
  if ($aarch64)
    qemu
  end
end

document hook-start
Binds the qemu command to the start command for aarch64 binaries.
end

define hook-run
  set-aarch64
  if ($aarch64)
    set $isRun = 1
    qemu
  end
end

document hook-run
Binds the qemu command to the run command for aarch64 binaries.
end

define set-aarch64
  python
if ("aarch64" in gdb.execute("info target", False, True)):
    gdb.execute("set $aarch64 = 1", False, True)
else:
    gdb.execute("set $aarch64 = 0", False, True)
  end
end

document set-aarch64
Sets convenience variable $aarch64 if the target architecture is
aarch64.
end

python
import os.path
import re
import subprocess

class QemuCommand(gdb.Command):
    """The qemu command starts the current target using Qemu on port
1234."""
    def __init__ (self):
        super(QemuCommand, self).__init__("qemu", gdb.COMMAND_RUNNING,
            gdb.COMPLETE_FILENAME)

    def invoke (self, args, from_tty):
        ## Get target
        it = gdb.execute("info target", False, True)
        target = re.search(r"/.*', file type", it)
        if target:
            target = target.group(0)[:-12]
            gdb.execute("set $target = \"" + target + "\"", True)
        else:
            raise gdb.GdbError("No symbol table loaded.  " +
                  "Use the \"file\" command.")

        ## Check target architecture
        if (not re.search(r"file type elf64-.*aarch64.", it)):
            raise gdb.GdbError("Target binary file must be aarch64.")

        ## Check GDB Mode
        if ("aarch64" not in
            gdb.execute("show architecture", from_tty, True)):
            raise gdb.GdbError("Please restart GDB with an aarch64 " +
                "binary. Multiple architectures cannot \nbe debugged " +
                "in the same session.")

        ## Kill existing processes
        try:
            gdb.execute("kill", False, True)
        except:
            pass
        subprocess.call("kill -9 $(ps -u | grep -m 1 'qemu-aarch64 -g" +
            " 1234' | awk '{print $2}') 2>/dev/null", shell=True)

        ## Reselect file
        gdb.execute("file " + target, from_tty, True)
        gdb.execute("set remote exec-file " + target, from_tty, True)

        ## Get Args
        if (not args):
            c = gdb.execute("show commands", False, True)
            args = re.findall(r"((\n|^) *\d+  (start |run |r ).*)", c)
            if (len(args)):
                args = args[-1][0].strip("\n");
                if (args[7:10] == "run"):
                    args = args[11:]
                elif (args[7] == "r"):
                    args = args[9:]
                else: # == "start"
                    args = args[13:]
            if (not args or args == ""):
                args = gdb.execute("show args", False, True)[68:-3]
        gdb.execute("set args " + args, False, True)

        ## Start
        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)
        isRun = gdb.execute("output $isRun", from_tty, True)
        if ("1" in isRun):
            gdb.execute("set $isRun = 0", from_tty, True)
        else:
            gdb.execute("tbreak main", from_tty, True)
        gdb.execute("continue", from_tty)
        raise gdb.GdbError("(gdb)")

QemuCommand()

end

Emacs Note

The following part, if placed in a ~/.emacs file will automatically switch gud-gdb to use gdb-multiarch instead of gdb. Using the command M-x gud-gdb is recommended for this setup.

(defcustom gud-gud-gdb-command-name "gdb-multiarch --fullname"
    "Default command to run an executable under GDB in text command mode.
    The option \"--fullname\" must be included in this value."
    :type 'string
    :group 'gud)