qemu/scripts/dump-guest-memory.py
<<
>>
Prefs
   1"""
   2This python script adds a new gdb command, "dump-guest-memory". It
   3should be loaded with "source dump-guest-memory.py" at the (gdb)
   4prompt.
   5
   6Copyright (C) 2013, Red Hat, Inc.
   7
   8Authors:
   9   Laszlo Ersek <lersek@redhat.com>
  10   Janosch Frank <frankja@linux.vnet.ibm.com>
  11
  12This work is licensed under the terms of the GNU GPL, version 2 or later. See
  13the COPYING file in the top-level directory.
  14"""
  15from __future__ import print_function
  16
  17import ctypes
  18import struct
  19
  20try:
  21    UINTPTR_T = gdb.lookup_type("uintptr_t")
  22except Exception as inst:
  23    raise gdb.GdbError("Symbols must be loaded prior to sourcing dump-guest-memory.\n"
  24                       "Symbols may be loaded by 'attach'ing a QEMU process id or by "
  25                       "'load'ing a QEMU binary.")
  26
  27TARGET_PAGE_SIZE = 0x1000
  28TARGET_PAGE_MASK = 0xFFFFFFFFFFFFF000
  29
  30# Special value for e_phnum. This indicates that the real number of
  31# program headers is too large to fit into e_phnum. Instead the real
  32# value is in the field sh_info of section 0.
  33PN_XNUM = 0xFFFF
  34
  35EV_CURRENT = 1
  36
  37ELFCLASS32 = 1
  38ELFCLASS64 = 2
  39
  40ELFDATA2LSB = 1
  41ELFDATA2MSB = 2
  42
  43ET_CORE = 4
  44
  45PT_LOAD = 1
  46PT_NOTE = 4
  47
  48EM_386 = 3
  49EM_PPC = 20
  50EM_PPC64 = 21
  51EM_S390 = 22
  52EM_AARCH = 183
  53EM_X86_64 = 62
  54
  55VMCOREINFO_FORMAT_ELF = 1
  56
  57def le16_to_cpu(val):
  58    return struct.unpack("<H", struct.pack("=H", val))[0]
  59
  60def le32_to_cpu(val):
  61    return struct.unpack("<I", struct.pack("=I", val))[0]
  62
  63def le64_to_cpu(val):
  64    return struct.unpack("<Q", struct.pack("=Q", val))[0]
  65
  66class ELF(object):
  67    """Representation of a ELF file."""
  68
  69    def __init__(self, arch):
  70        self.ehdr = None
  71        self.notes = []
  72        self.segments = []
  73        self.notes_size = 0
  74        self.endianness = None
  75        self.elfclass = ELFCLASS64
  76
  77        if arch == 'aarch64-le':
  78            self.endianness = ELFDATA2LSB
  79            self.elfclass = ELFCLASS64
  80            self.ehdr = get_arch_ehdr(self.endianness, self.elfclass)
  81            self.ehdr.e_machine = EM_AARCH
  82
  83        elif arch == 'aarch64-be':
  84            self.endianness = ELFDATA2MSB
  85            self.ehdr = get_arch_ehdr(self.endianness, self.elfclass)
  86            self.ehdr.e_machine = EM_AARCH
  87
  88        elif arch == 'X86_64':
  89            self.endianness = ELFDATA2LSB
  90            self.ehdr = get_arch_ehdr(self.endianness, self.elfclass)
  91            self.ehdr.e_machine = EM_X86_64
  92
  93        elif arch == '386':
  94            self.endianness = ELFDATA2LSB
  95            self.elfclass = ELFCLASS32
  96            self.ehdr = get_arch_ehdr(self.endianness, self.elfclass)
  97            self.ehdr.e_machine = EM_386
  98
  99        elif arch == 's390':
 100            self.endianness = ELFDATA2MSB
 101            self.ehdr = get_arch_ehdr(self.endianness, self.elfclass)
 102            self.ehdr.e_machine = EM_S390
 103
 104        elif arch == 'ppc64-le':
 105            self.endianness = ELFDATA2LSB
 106            self.ehdr = get_arch_ehdr(self.endianness, self.elfclass)
 107            self.ehdr.e_machine = EM_PPC64
 108
 109        elif arch == 'ppc64-be':
 110            self.endianness = ELFDATA2MSB
 111            self.ehdr = get_arch_ehdr(self.endianness, self.elfclass)
 112            self.ehdr.e_machine = EM_PPC64
 113
 114        else:
 115            raise gdb.GdbError("No valid arch type specified.\n"
 116                               "Currently supported types:\n"
 117                               "aarch64-be, aarch64-le, X86_64, 386, s390, "
 118                               "ppc64-be, ppc64-le")
 119
 120        self.add_segment(PT_NOTE, 0, 0)
 121
 122    def add_note(self, n_name, n_desc, n_type):
 123        """Adds a note to the ELF."""
 124
 125        note = get_arch_note(self.endianness, len(n_name), len(n_desc))
 126        note.n_namesz = len(n_name) + 1
 127        note.n_descsz = len(n_desc)
 128        note.n_name = n_name.encode()
 129        note.n_type = n_type
 130
 131        # Desc needs to be 4 byte aligned (although the 64bit spec
 132        # specifies 8 byte). When defining n_desc as uint32 it will be
 133        # automatically aligned but we need the memmove to copy the
 134        # string into it.
 135        ctypes.memmove(note.n_desc, n_desc.encode(), len(n_desc))
 136
 137        self.notes.append(note)
 138        self.segments[0].p_filesz += ctypes.sizeof(note)
 139        self.segments[0].p_memsz += ctypes.sizeof(note)
 140
 141
 142    def add_vmcoreinfo_note(self, vmcoreinfo):
 143        """Adds a vmcoreinfo note to the ELF dump."""
 144        # compute the header size, and copy that many bytes from the note
 145        header = get_arch_note(self.endianness, 0, 0)
 146        ctypes.memmove(ctypes.pointer(header),
 147                       vmcoreinfo, ctypes.sizeof(header))
 148        if header.n_descsz > 1 << 20:
 149            print('warning: invalid vmcoreinfo size')
 150            return
 151        # now get the full note
 152        note = get_arch_note(self.endianness,
 153                             header.n_namesz - 1, header.n_descsz)
 154        ctypes.memmove(ctypes.pointer(note), vmcoreinfo, ctypes.sizeof(note))
 155
 156        self.notes.append(note)
 157        self.segments[0].p_filesz += ctypes.sizeof(note)
 158        self.segments[0].p_memsz += ctypes.sizeof(note)
 159
 160    def add_segment(self, p_type, p_paddr, p_size):
 161        """Adds a segment to the elf."""
 162
 163        phdr = get_arch_phdr(self.endianness, self.elfclass)
 164        phdr.p_type = p_type
 165        phdr.p_paddr = p_paddr
 166        phdr.p_filesz = p_size
 167        phdr.p_memsz = p_size
 168        self.segments.append(phdr)
 169        self.ehdr.e_phnum += 1
 170
 171    def to_file(self, elf_file):
 172        """Writes all ELF structures to the the passed file.
 173
 174        Structure:
 175        Ehdr
 176        Segment 0:PT_NOTE
 177        Segment 1:PT_LOAD
 178        Segment N:PT_LOAD
 179        Note    0..N
 180        Dump contents
 181        """
 182        elf_file.write(self.ehdr)
 183        off = ctypes.sizeof(self.ehdr) + \
 184              len(self.segments) * ctypes.sizeof(self.segments[0])
 185
 186        for phdr in self.segments:
 187            phdr.p_offset = off
 188            elf_file.write(phdr)
 189            off += phdr.p_filesz
 190
 191        for note in self.notes:
 192            elf_file.write(note)
 193
 194
 195def get_arch_note(endianness, len_name, len_desc):
 196    """Returns a Note class with the specified endianness."""
 197
 198    if endianness == ELFDATA2LSB:
 199        superclass = ctypes.LittleEndianStructure
 200    else:
 201        superclass = ctypes.BigEndianStructure
 202
 203    len_name = len_name + 1
 204
 205    class Note(superclass):
 206        """Represents an ELF note, includes the content."""
 207
 208        _fields_ = [("n_namesz", ctypes.c_uint32),
 209                    ("n_descsz", ctypes.c_uint32),
 210                    ("n_type", ctypes.c_uint32),
 211                    ("n_name", ctypes.c_char * len_name),
 212                    ("n_desc", ctypes.c_uint32 * ((len_desc + 3) // 4))]
 213    return Note()
 214
 215
 216class Ident(ctypes.Structure):
 217    """Represents the ELF ident array in the ehdr structure."""
 218
 219    _fields_ = [('ei_mag0', ctypes.c_ubyte),
 220                ('ei_mag1', ctypes.c_ubyte),
 221                ('ei_mag2', ctypes.c_ubyte),
 222                ('ei_mag3', ctypes.c_ubyte),
 223                ('ei_class', ctypes.c_ubyte),
 224                ('ei_data', ctypes.c_ubyte),
 225                ('ei_version', ctypes.c_ubyte),
 226                ('ei_osabi', ctypes.c_ubyte),
 227                ('ei_abiversion', ctypes.c_ubyte),
 228                ('ei_pad', ctypes.c_ubyte * 7)]
 229
 230    def __init__(self, endianness, elfclass):
 231        self.ei_mag0 = 0x7F
 232        self.ei_mag1 = ord('E')
 233        self.ei_mag2 = ord('L')
 234        self.ei_mag3 = ord('F')
 235        self.ei_class = elfclass
 236        self.ei_data = endianness
 237        self.ei_version = EV_CURRENT
 238
 239
 240def get_arch_ehdr(endianness, elfclass):
 241    """Returns a EHDR64 class with the specified endianness."""
 242
 243    if endianness == ELFDATA2LSB:
 244        superclass = ctypes.LittleEndianStructure
 245    else:
 246        superclass = ctypes.BigEndianStructure
 247
 248    class EHDR64(superclass):
 249        """Represents the 64 bit ELF header struct."""
 250
 251        _fields_ = [('e_ident', Ident),
 252                    ('e_type', ctypes.c_uint16),
 253                    ('e_machine', ctypes.c_uint16),
 254                    ('e_version', ctypes.c_uint32),
 255                    ('e_entry', ctypes.c_uint64),
 256                    ('e_phoff', ctypes.c_uint64),
 257                    ('e_shoff', ctypes.c_uint64),
 258                    ('e_flags', ctypes.c_uint32),
 259                    ('e_ehsize', ctypes.c_uint16),
 260                    ('e_phentsize', ctypes.c_uint16),
 261                    ('e_phnum', ctypes.c_uint16),
 262                    ('e_shentsize', ctypes.c_uint16),
 263                    ('e_shnum', ctypes.c_uint16),
 264                    ('e_shstrndx', ctypes.c_uint16)]
 265
 266        def __init__(self):
 267            super(superclass, self).__init__()
 268            self.e_ident = Ident(endianness, elfclass)
 269            self.e_type = ET_CORE
 270            self.e_version = EV_CURRENT
 271            self.e_ehsize = ctypes.sizeof(self)
 272            self.e_phoff = ctypes.sizeof(self)
 273            self.e_phentsize = ctypes.sizeof(get_arch_phdr(endianness, elfclass))
 274            self.e_phnum = 0
 275
 276
 277    class EHDR32(superclass):
 278        """Represents the 32 bit ELF header struct."""
 279
 280        _fields_ = [('e_ident', Ident),
 281                    ('e_type', ctypes.c_uint16),
 282                    ('e_machine', ctypes.c_uint16),
 283                    ('e_version', ctypes.c_uint32),
 284                    ('e_entry', ctypes.c_uint32),
 285                    ('e_phoff', ctypes.c_uint32),
 286                    ('e_shoff', ctypes.c_uint32),
 287                    ('e_flags', ctypes.c_uint32),
 288                    ('e_ehsize', ctypes.c_uint16),
 289                    ('e_phentsize', ctypes.c_uint16),
 290                    ('e_phnum', ctypes.c_uint16),
 291                    ('e_shentsize', ctypes.c_uint16),
 292                    ('e_shnum', ctypes.c_uint16),
 293                    ('e_shstrndx', ctypes.c_uint16)]
 294
 295        def __init__(self):
 296            super(superclass, self).__init__()
 297            self.e_ident = Ident(endianness, elfclass)
 298            self.e_type = ET_CORE
 299            self.e_version = EV_CURRENT
 300            self.e_ehsize = ctypes.sizeof(self)
 301            self.e_phoff = ctypes.sizeof(self)
 302            self.e_phentsize = ctypes.sizeof(get_arch_phdr(endianness, elfclass))
 303            self.e_phnum = 0
 304
 305    # End get_arch_ehdr
 306    if elfclass == ELFCLASS64:
 307        return EHDR64()
 308    else:
 309        return EHDR32()
 310
 311
 312def get_arch_phdr(endianness, elfclass):
 313    """Returns a 32 or 64 bit PHDR class with the specified endianness."""
 314
 315    if endianness == ELFDATA2LSB:
 316        superclass = ctypes.LittleEndianStructure
 317    else:
 318        superclass = ctypes.BigEndianStructure
 319
 320    class PHDR64(superclass):
 321        """Represents the 64 bit ELF program header struct."""
 322
 323        _fields_ = [('p_type', ctypes.c_uint32),
 324                    ('p_flags', ctypes.c_uint32),
 325                    ('p_offset', ctypes.c_uint64),
 326                    ('p_vaddr', ctypes.c_uint64),
 327                    ('p_paddr', ctypes.c_uint64),
 328                    ('p_filesz', ctypes.c_uint64),
 329                    ('p_memsz', ctypes.c_uint64),
 330                    ('p_align', ctypes.c_uint64)]
 331
 332    class PHDR32(superclass):
 333        """Represents the 32 bit ELF program header struct."""
 334
 335        _fields_ = [('p_type', ctypes.c_uint32),
 336                    ('p_offset', ctypes.c_uint32),
 337                    ('p_vaddr', ctypes.c_uint32),
 338                    ('p_paddr', ctypes.c_uint32),
 339                    ('p_filesz', ctypes.c_uint32),
 340                    ('p_memsz', ctypes.c_uint32),
 341                    ('p_flags', ctypes.c_uint32),
 342                    ('p_align', ctypes.c_uint32)]
 343
 344    # End get_arch_phdr
 345    if elfclass == ELFCLASS64:
 346        return PHDR64()
 347    else:
 348        return PHDR32()
 349
 350
 351def int128_get64(val):
 352    """Returns low 64bit part of Int128 struct."""
 353
 354    try:
 355        assert val["hi"] == 0
 356        return val["lo"]
 357    except gdb.error:
 358        u64t = gdb.lookup_type('uint64_t').array(2)
 359        u64 = val.cast(u64t)
 360        if sys.byteorder == 'little':
 361            assert u64[1] == 0
 362            return u64[0]
 363        else:
 364            assert u64[0] == 0
 365            return u64[1]
 366
 367
 368def qlist_foreach(head, field_str):
 369    """Generator for qlists."""
 370
 371    var_p = head["lh_first"]
 372    while var_p != 0:
 373        var = var_p.dereference()
 374        var_p = var[field_str]["le_next"]
 375        yield var
 376
 377
 378def qemu_map_ram_ptr(block, offset):
 379    """Returns qemu vaddr for given guest physical address."""
 380
 381    return block["host"] + offset
 382
 383
 384def memory_region_get_ram_ptr(memory_region):
 385    if memory_region["alias"] != 0:
 386        return (memory_region_get_ram_ptr(memory_region["alias"].dereference())
 387                + memory_region["alias_offset"])
 388
 389    return qemu_map_ram_ptr(memory_region["ram_block"], 0)
 390
 391
 392def get_guest_phys_blocks():
 393    """Returns a list of ram blocks.
 394
 395    Each block entry contains:
 396    'target_start': guest block phys start address
 397    'target_end':   guest block phys end address
 398    'host_addr':    qemu vaddr of the block's start
 399    """
 400
 401    guest_phys_blocks = []
 402
 403    print("guest RAM blocks:")
 404    print("target_start     target_end       host_addr        message "
 405          "count")
 406    print("---------------- ---------------- ---------------- ------- "
 407          "-----")
 408
 409    current_map_p = gdb.parse_and_eval("address_space_memory.current_map")
 410    current_map = current_map_p.dereference()
 411
 412    # Conversion to int is needed for python 3
 413    # compatibility. Otherwise range doesn't cast the value itself and
 414    # breaks.
 415    for cur in range(int(current_map["nr"])):
 416        flat_range = (current_map["ranges"] + cur).dereference()
 417        memory_region = flat_range["mr"].dereference()
 418
 419        # we only care about RAM
 420        if not memory_region["ram"]:
 421            continue
 422
 423        section_size = int128_get64(flat_range["addr"]["size"])
 424        target_start = int128_get64(flat_range["addr"]["start"])
 425        target_end = target_start + section_size
 426        host_addr = (memory_region_get_ram_ptr(memory_region)
 427                     + flat_range["offset_in_region"])
 428        predecessor = None
 429
 430        # find continuity in guest physical address space
 431        if len(guest_phys_blocks) > 0:
 432            predecessor = guest_phys_blocks[-1]
 433            predecessor_size = (predecessor["target_end"] -
 434                                predecessor["target_start"])
 435
 436            # the memory API guarantees monotonically increasing
 437            # traversal
 438            assert predecessor["target_end"] <= target_start
 439
 440            # we want continuity in both guest-physical and
 441            # host-virtual memory
 442            if (predecessor["target_end"] < target_start or
 443                predecessor["host_addr"] + predecessor_size != host_addr):
 444                predecessor = None
 445
 446        if predecessor is None:
 447            # isolated mapping, add it to the list
 448            guest_phys_blocks.append({"target_start": target_start,
 449                                      "target_end":   target_end,
 450                                      "host_addr":    host_addr})
 451            message = "added"
 452        else:
 453            # expand predecessor until @target_end; predecessor's
 454            # start doesn't change
 455            predecessor["target_end"] = target_end
 456            message = "joined"
 457
 458        print("%016x %016x %016x %-7s %5u" %
 459              (target_start, target_end, host_addr.cast(UINTPTR_T),
 460               message, len(guest_phys_blocks)))
 461
 462    return guest_phys_blocks
 463
 464
 465# The leading docstring doesn't have idiomatic Python formatting. It is
 466# printed by gdb's "help" command (the first line is printed in the
 467# "help data" summary), and it should match how other help texts look in
 468# gdb.
 469class DumpGuestMemory(gdb.Command):
 470    """Extract guest vmcore from qemu process coredump.
 471
 472The two required arguments are FILE and ARCH:
 473FILE identifies the target file to write the guest vmcore to.
 474ARCH specifies the architecture for which the core will be generated.
 475
 476This GDB command reimplements the dump-guest-memory QMP command in
 477python, using the representation of guest memory as captured in the qemu
 478coredump. The qemu process that has been dumped must have had the
 479command line option "-machine dump-guest-core=on" which is the default.
 480
 481For simplicity, the "paging", "begin" and "end" parameters of the QMP
 482command are not supported -- no attempt is made to get the guest's
 483internal paging structures (ie. paging=false is hard-wired), and guest
 484memory is always fully dumped.
 485
 486Currently aarch64-be, aarch64-le, X86_64, 386, s390, ppc64-be,
 487ppc64-le guests are supported.
 488
 489The CORE/NT_PRSTATUS and QEMU notes (that is, the VCPUs' statuses) are
 490not written to the vmcore. Preparing these would require context that is
 491only present in the KVM host kernel module when the guest is alive. A
 492fake ELF note is written instead, only to keep the ELF parser of "crash"
 493happy.
 494
 495Dependent on how busted the qemu process was at the time of the
 496coredump, this command might produce unpredictable results. If qemu
 497deliberately called abort(), or it was dumped in response to a signal at
 498a halfway fortunate point, then its coredump should be in reasonable
 499shape and this command should mostly work."""
 500
 501    def __init__(self):
 502        super(DumpGuestMemory, self).__init__("dump-guest-memory",
 503                                              gdb.COMMAND_DATA,
 504                                              gdb.COMPLETE_FILENAME)
 505        self.elf = None
 506        self.guest_phys_blocks = None
 507
 508    def dump_init(self, vmcore):
 509        """Prepares and writes ELF structures to core file."""
 510
 511        # Needed to make crash happy, data for more useful notes is
 512        # not available in a qemu core.
 513        self.elf.add_note("NONE", "EMPTY", 0)
 514
 515        # We should never reach PN_XNUM for paging=false dumps,
 516        # there's just a handful of discontiguous ranges after
 517        # merging.
 518        # The constant is needed to account for the PT_NOTE segment.
 519        phdr_num = len(self.guest_phys_blocks) + 1
 520        assert phdr_num < PN_XNUM
 521
 522        for block in self.guest_phys_blocks:
 523            block_size = block["target_end"] - block["target_start"]
 524            self.elf.add_segment(PT_LOAD, block["target_start"], block_size)
 525
 526        self.elf.to_file(vmcore)
 527
 528    def dump_iterate(self, vmcore):
 529        """Writes guest core to file."""
 530
 531        qemu_core = gdb.inferiors()[0]
 532        for block in self.guest_phys_blocks:
 533            cur = block["host_addr"]
 534            left = block["target_end"] - block["target_start"]
 535            print("dumping range at %016x for length %016x" %
 536                  (cur.cast(UINTPTR_T), left))
 537
 538            while left > 0:
 539                chunk_size = min(TARGET_PAGE_SIZE, left)
 540                chunk = qemu_core.read_memory(cur, chunk_size)
 541                vmcore.write(chunk)
 542                cur += chunk_size
 543                left -= chunk_size
 544
 545    def phys_memory_read(self, addr, size):
 546        qemu_core = gdb.inferiors()[0]
 547        for block in self.guest_phys_blocks:
 548            if block["target_start"] <= addr \
 549               and addr + size <= block["target_end"]:
 550                haddr = block["host_addr"] + (addr - block["target_start"])
 551                return qemu_core.read_memory(haddr, size)
 552        return None
 553
 554    def add_vmcoreinfo(self):
 555        if gdb.lookup_symbol("vmcoreinfo_realize")[0] is None:
 556            return
 557        vmci = 'vmcoreinfo_realize::vmcoreinfo_state'
 558        if not gdb.parse_and_eval("%s" % vmci) \
 559           or not gdb.parse_and_eval("(%s)->has_vmcoreinfo" % vmci):
 560            return
 561
 562        fmt = gdb.parse_and_eval("(%s)->vmcoreinfo.guest_format" % vmci)
 563        addr = gdb.parse_and_eval("(%s)->vmcoreinfo.paddr" % vmci)
 564        size = gdb.parse_and_eval("(%s)->vmcoreinfo.size" % vmci)
 565
 566        fmt = le16_to_cpu(fmt)
 567        addr = le64_to_cpu(addr)
 568        size = le32_to_cpu(size)
 569
 570        if fmt != VMCOREINFO_FORMAT_ELF:
 571            return
 572
 573        vmcoreinfo = self.phys_memory_read(addr, size)
 574        if vmcoreinfo:
 575            self.elf.add_vmcoreinfo_note(bytes(vmcoreinfo))
 576
 577    def invoke(self, args, from_tty):
 578        """Handles command invocation from gdb."""
 579
 580        # Unwittingly pressing the Enter key after the command should
 581        # not dump the same multi-gig coredump to the same file.
 582        self.dont_repeat()
 583
 584        argv = gdb.string_to_argv(args)
 585        if len(argv) != 2:
 586            raise gdb.GdbError("usage: dump-guest-memory FILE ARCH")
 587
 588        self.elf = ELF(argv[1])
 589        self.guest_phys_blocks = get_guest_phys_blocks()
 590        self.add_vmcoreinfo()
 591
 592        with open(argv[0], "wb") as vmcore:
 593            self.dump_init(vmcore)
 594            self.dump_iterate(vmcore)
 595
 596DumpGuestMemory()
 597