Skip to content
  • David Johnson's avatar
    Add a GDB backend, with "plugin" support for QEMU! · 361cf775
    David Johnson authored
    The main goal of this is QEMU support, of course.  It just so
    happened that the best way to do it was to add a GDB stub backend
    (because QEMU provides a GDB stub server); and make that GDB backend
    capable of platform/stub-specific extensions/helper functions.
    So, we get QEMU support -- and we can hopefully use the GDB backend
    to automatically get support for other kinds of targets (like KGDB).
    Realistically, I implemented mostly what I needed for QEMU, so I'll
    probably have to beef up my support for the GDB remote protocol to
    truly support other target types that have a GDB stub.
    
    First, there is a GDB backend that speaks the GDB remote protocol.
    This is enough for it to r/w memory, most x86/x86_64 GP regs, and
    set/unset breakpoints and single step.  Second, the GDB backend allows
    "helpers" to be plugged in to enhance its functionality.  For instance,
    GDB can only read a single kind of memory; it doesn't know about
    virtual vs physical memory.  Second, at least the part of the GDB
    remote protocol I implemented to read/write registers (the old
    bulk style, not the "new" XML style, ugh) isn't real amenable to
    extension... i.e., it mostly supports GP regs, not control registers
    or MSRs.
    
    The helper interface supports reading/writing virt/phys mem, and
    loading the "machine" (i.e., MSRs or other extra regs).  This is
    necessary for QEMU, and to make the os_linux_generic OS personality
    able to sit atop the QEMU target (we need some control registers
    and an MSR).  Thus, there is a "builtin" helper that speaks GDB's
    remote protocol to read/write memory -- and there is a QEMU helper
    that can load extra registers (by speaking QMP -- the QEMU monitor
    protocol (JSON -- ugh!)); AND can potentially read/write physical
    memory if QEMU is run 1) on linux with hugetlbfs and the -mem-path
    option, and 2) with our libqemuhacks.so library LD_PRELOADed and
    configured correctly!
    
    The QEMU helper is interesting.  It not only reads/writes physical
    memory by mmap'ing the memory file QEMU created (and that
    libqemuhacks.so prevented QEMU from deleting, and made shareable
    via MAP_SHARED instead of MAP_PRIVATE); but it also sends all
    virtual mem reads/writes over this "channel" to physical memory.
    This is much, much, much faster that using GDB to read the current
    thread's virtual memory!  Moreover, the QEMU GDB stub seems to
    not be able to read virtual memory chunks -- I've crashed the VM
    while trying -- so I only read unknown-length things like strings
    for which I have to scan for a '\0' using wordsize-chunk reads.
    
    The QEMU helper is helped by the libqemuhacks.so LD_PRELOADable
    shared lib.  The way this is used is when you start QEMU, like
    
      sudo QEMU_MEMPATH_PREFIX=/hugetlbfs/qemu \
        LD_PRELOAD=/opt/vmi/lib/libqemuhacks.so.0.0.0 \
        qemu-system-x86_64 -cpu host -m 512 -enable-kvm \
          -kernel /tftpboot/vmlinuz-2.6.18-308.el5 \
          -initrd /tftpboot/automfs-2.6.18-308.el5.img \
          -append console=ttyS0 -nographic -gdb tcp::1234 \
          -qmp tcp:127.0.0.1:1235,server,nowait \
          -mem-path /hugetlbfs
    
    libqemuhacks.so gets loaded, and it overloads unlink() and mmap()
    in libc.  This is because when QEMU mallocs its big hunk of
    memory to serve as the guest's physical memory, it does so by
    creating a file on a hugetlbfs-backend mountpoint, backing it
    with a MAP_PRIVATE mmap, and immediately unlink()ing it.  This
    prevents our backend from being able to mmap() that file and
    read/write physical memory directly.  So -- libqemuhacks.so
    looks for unlink()s of any files whose pathname starts with
    QEMU_MEMPATH_PREFIX (in the case above, /hugetlbfs/qemu, because
    my hugetlbfs was mounted at /hugetlbfs, and qemu creates its RAM
    files starting with "qemu".  After neutralizing the unlink(),
    libqemuhacks.so then makes sure that any mmap using an fd whose
    pathname starts with QEMU_MEMPATH_PREFIX is mmap'd with MAP_SHARED
    instead of MAP_PRIVATE.  That's it -- then we can mmap it too --
    without having to know about the internals of QEMU too much :).
    I love LD_PRELOAD; this way I didn't have to patch QEMU!
    (One little note is that the RAM files don't get unlink'd, so
    they appear in the filesystem -- BUT they don't appear to be using
    hugepages once QEMU closes.  I can't explain this yet... but it'll
    be trivial to add support to store the non-unlink'd pathnames in
    libqemuhacks.so, and unlink them via atexit().)
    
    There were a bunch of changes necessary to handle targets that
    manage breakpoints and the breakpoint IP themselves, like the GDB
    stub does.
    
    Oh, I haven't done hardware breakpoints yet; the backend interface
    has to change a bit for that for the GDB backend.  Also, there are
    still some complex multi-thread OS cases I need to port from the
    Xen backend into the GDB backend.
    
    The GDB protocol is ugly.  I'm just going to leave it at that.
    361cf775