summaryrefslogtreecommitdiffstats
path: root/qemu/scripts/dump-guest-memory.py
diff options
context:
space:
mode:
Diffstat (limited to 'qemu/scripts/dump-guest-memory.py')
-rw-r--r--qemu/scripts/dump-guest-memory.py529
1 files changed, 0 insertions, 529 deletions
diff --git a/qemu/scripts/dump-guest-memory.py b/qemu/scripts/dump-guest-memory.py
deleted file mode 100644
index c0a2e99f4..000000000
--- a/qemu/scripts/dump-guest-memory.py
+++ /dev/null
@@ -1,529 +0,0 @@
-"""
-This python script adds a new gdb command, "dump-guest-memory". It
-should be loaded with "source dump-guest-memory.py" at the (gdb)
-prompt.
-
-Copyright (C) 2013, Red Hat, Inc.
-
-Authors:
- Laszlo Ersek <lersek@redhat.com>
- Janosch Frank <frankja@linux.vnet.ibm.com>
-
-This work is licensed under the terms of the GNU GPL, version 2 or later. See
-the COPYING file in the top-level directory.
-"""
-
-import ctypes
-
-UINTPTR_T = gdb.lookup_type("uintptr_t")
-
-TARGET_PAGE_SIZE = 0x1000
-TARGET_PAGE_MASK = 0xFFFFFFFFFFFFF000
-
-# Special value for e_phnum. This indicates that the real number of
-# program headers is too large to fit into e_phnum. Instead the real
-# value is in the field sh_info of section 0.
-PN_XNUM = 0xFFFF
-
-EV_CURRENT = 1
-
-ELFCLASS32 = 1
-ELFCLASS64 = 2
-
-ELFDATA2LSB = 1
-ELFDATA2MSB = 2
-
-ET_CORE = 4
-
-PT_LOAD = 1
-PT_NOTE = 4
-
-EM_386 = 3
-EM_PPC = 20
-EM_PPC64 = 21
-EM_S390 = 22
-EM_AARCH = 183
-EM_X86_64 = 62
-
-class ELF(object):
- """Representation of a ELF file."""
-
- def __init__(self, arch):
- self.ehdr = None
- self.notes = []
- self.segments = []
- self.notes_size = 0
- self.endianess = None
- self.elfclass = ELFCLASS64
-
- if arch == 'aarch64-le':
- self.endianess = ELFDATA2LSB
- self.elfclass = ELFCLASS64
- self.ehdr = get_arch_ehdr(self.endianess, self.elfclass)
- self.ehdr.e_machine = EM_AARCH
-
- elif arch == 'aarch64-be':
- self.endianess = ELFDATA2MSB
- self.ehdr = get_arch_ehdr(self.endianess, self.elfclass)
- self.ehdr.e_machine = EM_AARCH
-
- elif arch == 'X86_64':
- self.endianess = ELFDATA2LSB
- self.ehdr = get_arch_ehdr(self.endianess, self.elfclass)
- self.ehdr.e_machine = EM_X86_64
-
- elif arch == '386':
- self.endianess = ELFDATA2LSB
- self.elfclass = ELFCLASS32
- self.ehdr = get_arch_ehdr(self.endianess, self.elfclass)
- self.ehdr.e_machine = EM_386
-
- elif arch == 's390':
- self.endianess = ELFDATA2MSB
- self.ehdr = get_arch_ehdr(self.endianess, self.elfclass)
- self.ehdr.e_machine = EM_S390
-
- elif arch == 'ppc64-le':
- self.endianess = ELFDATA2LSB
- self.ehdr = get_arch_ehdr(self.endianess, self.elfclass)
- self.ehdr.e_machine = EM_PPC64
-
- elif arch == 'ppc64-be':
- self.endianess = ELFDATA2MSB
- self.ehdr = get_arch_ehdr(self.endianess, self.elfclass)
- self.ehdr.e_machine = EM_PPC64
-
- else:
- raise gdb.GdbError("No valid arch type specified.\n"
- "Currently supported types:\n"
- "aarch64-be, aarch64-le, X86_64, 386, s390, "
- "ppc64-be, ppc64-le")
-
- self.add_segment(PT_NOTE, 0, 0)
-
- def add_note(self, n_name, n_desc, n_type):
- """Adds a note to the ELF."""
-
- note = get_arch_note(self.endianess, len(n_name), len(n_desc))
- note.n_namesz = len(n_name) + 1
- note.n_descsz = len(n_desc)
- note.n_name = n_name.encode()
- note.n_type = n_type
-
- # Desc needs to be 4 byte aligned (although the 64bit spec
- # specifies 8 byte). When defining n_desc as uint32 it will be
- # automatically aligned but we need the memmove to copy the
- # string into it.
- ctypes.memmove(note.n_desc, n_desc.encode(), len(n_desc))
-
- self.notes.append(note)
- self.segments[0].p_filesz += ctypes.sizeof(note)
- self.segments[0].p_memsz += ctypes.sizeof(note)
-
- def add_segment(self, p_type, p_paddr, p_size):
- """Adds a segment to the elf."""
-
- phdr = get_arch_phdr(self.endianess, self.elfclass)
- phdr.p_type = p_type
- phdr.p_paddr = p_paddr
- phdr.p_filesz = p_size
- phdr.p_memsz = p_size
- self.segments.append(phdr)
- self.ehdr.e_phnum += 1
-
- def to_file(self, elf_file):
- """Writes all ELF structures to the the passed file.
-
- Structure:
- Ehdr
- Segment 0:PT_NOTE
- Segment 1:PT_LOAD
- Segment N:PT_LOAD
- Note 0..N
- Dump contents
- """
- elf_file.write(self.ehdr)
- off = ctypes.sizeof(self.ehdr) + \
- len(self.segments) * ctypes.sizeof(self.segments[0])
-
- for phdr in self.segments:
- phdr.p_offset = off
- elf_file.write(phdr)
- off += phdr.p_filesz
-
- for note in self.notes:
- elf_file.write(note)
-
-
-def get_arch_note(endianess, len_name, len_desc):
- """Returns a Note class with the specified endianess."""
-
- if endianess == ELFDATA2LSB:
- superclass = ctypes.LittleEndianStructure
- else:
- superclass = ctypes.BigEndianStructure
-
- len_name = len_name + 1
-
- class Note(superclass):
- """Represents an ELF note, includes the content."""
-
- _fields_ = [("n_namesz", ctypes.c_uint32),
- ("n_descsz", ctypes.c_uint32),
- ("n_type", ctypes.c_uint32),
- ("n_name", ctypes.c_char * len_name),
- ("n_desc", ctypes.c_uint32 * ((len_desc + 3) // 4))]
- return Note()
-
-
-class Ident(ctypes.Structure):
- """Represents the ELF ident array in the ehdr structure."""
-
- _fields_ = [('ei_mag0', ctypes.c_ubyte),
- ('ei_mag1', ctypes.c_ubyte),
- ('ei_mag2', ctypes.c_ubyte),
- ('ei_mag3', ctypes.c_ubyte),
- ('ei_class', ctypes.c_ubyte),
- ('ei_data', ctypes.c_ubyte),
- ('ei_version', ctypes.c_ubyte),
- ('ei_osabi', ctypes.c_ubyte),
- ('ei_abiversion', ctypes.c_ubyte),
- ('ei_pad', ctypes.c_ubyte * 7)]
-
- def __init__(self, endianess, elfclass):
- self.ei_mag0 = 0x7F
- self.ei_mag1 = ord('E')
- self.ei_mag2 = ord('L')
- self.ei_mag3 = ord('F')
- self.ei_class = elfclass
- self.ei_data = endianess
- self.ei_version = EV_CURRENT
-
-
-def get_arch_ehdr(endianess, elfclass):
- """Returns a EHDR64 class with the specified endianess."""
-
- if endianess == ELFDATA2LSB:
- superclass = ctypes.LittleEndianStructure
- else:
- superclass = ctypes.BigEndianStructure
-
- class EHDR64(superclass):
- """Represents the 64 bit ELF header struct."""
-
- _fields_ = [('e_ident', Ident),
- ('e_type', ctypes.c_uint16),
- ('e_machine', ctypes.c_uint16),
- ('e_version', ctypes.c_uint32),
- ('e_entry', ctypes.c_uint64),
- ('e_phoff', ctypes.c_uint64),
- ('e_shoff', ctypes.c_uint64),
- ('e_flags', ctypes.c_uint32),
- ('e_ehsize', ctypes.c_uint16),
- ('e_phentsize', ctypes.c_uint16),
- ('e_phnum', ctypes.c_uint16),
- ('e_shentsize', ctypes.c_uint16),
- ('e_shnum', ctypes.c_uint16),
- ('e_shstrndx', ctypes.c_uint16)]
-
- def __init__(self):
- super(superclass, self).__init__()
- self.e_ident = Ident(endianess, elfclass)
- self.e_type = ET_CORE
- self.e_version = EV_CURRENT
- self.e_ehsize = ctypes.sizeof(self)
- self.e_phoff = ctypes.sizeof(self)
- self.e_phentsize = ctypes.sizeof(get_arch_phdr(endianess, elfclass))
- self.e_phnum = 0
-
-
- class EHDR32(superclass):
- """Represents the 32 bit ELF header struct."""
-
- _fields_ = [('e_ident', Ident),
- ('e_type', ctypes.c_uint16),
- ('e_machine', ctypes.c_uint16),
- ('e_version', ctypes.c_uint32),
- ('e_entry', ctypes.c_uint32),
- ('e_phoff', ctypes.c_uint32),
- ('e_shoff', ctypes.c_uint32),
- ('e_flags', ctypes.c_uint32),
- ('e_ehsize', ctypes.c_uint16),
- ('e_phentsize', ctypes.c_uint16),
- ('e_phnum', ctypes.c_uint16),
- ('e_shentsize', ctypes.c_uint16),
- ('e_shnum', ctypes.c_uint16),
- ('e_shstrndx', ctypes.c_uint16)]
-
- def __init__(self):
- super(superclass, self).__init__()
- self.e_ident = Ident(endianess, elfclass)
- self.e_type = ET_CORE
- self.e_version = EV_CURRENT
- self.e_ehsize = ctypes.sizeof(self)
- self.e_phoff = ctypes.sizeof(self)
- self.e_phentsize = ctypes.sizeof(get_arch_phdr(endianess, elfclass))
- self.e_phnum = 0
-
- # End get_arch_ehdr
- if elfclass == ELFCLASS64:
- return EHDR64()
- else:
- return EHDR32()
-
-
-def get_arch_phdr(endianess, elfclass):
- """Returns a 32 or 64 bit PHDR class with the specified endianess."""
-
- if endianess == ELFDATA2LSB:
- superclass = ctypes.LittleEndianStructure
- else:
- superclass = ctypes.BigEndianStructure
-
- class PHDR64(superclass):
- """Represents the 64 bit ELF program header struct."""
-
- _fields_ = [('p_type', ctypes.c_uint32),
- ('p_flags', ctypes.c_uint32),
- ('p_offset', ctypes.c_uint64),
- ('p_vaddr', ctypes.c_uint64),
- ('p_paddr', ctypes.c_uint64),
- ('p_filesz', ctypes.c_uint64),
- ('p_memsz', ctypes.c_uint64),
- ('p_align', ctypes.c_uint64)]
-
- class PHDR32(superclass):
- """Represents the 32 bit ELF program header struct."""
-
- _fields_ = [('p_type', ctypes.c_uint32),
- ('p_offset', ctypes.c_uint32),
- ('p_vaddr', ctypes.c_uint32),
- ('p_paddr', ctypes.c_uint32),
- ('p_filesz', ctypes.c_uint32),
- ('p_memsz', ctypes.c_uint32),
- ('p_flags', ctypes.c_uint32),
- ('p_align', ctypes.c_uint32)]
-
- # End get_arch_phdr
- if elfclass == ELFCLASS64:
- return PHDR64()
- else:
- return PHDR32()
-
-
-def int128_get64(val):
- """Returns low 64bit part of Int128 struct."""
-
- assert val["hi"] == 0
- return val["lo"]
-
-
-def qlist_foreach(head, field_str):
- """Generator for qlists."""
-
- var_p = head["lh_first"]
- while var_p != 0:
- var = var_p.dereference()
- var_p = var[field_str]["le_next"]
- yield var
-
-
-def qemu_get_ram_block(ram_addr):
- """Returns the RAMBlock struct to which the given address belongs."""
-
- ram_blocks = gdb.parse_and_eval("ram_list.blocks")
-
- for block in qlist_foreach(ram_blocks, "next"):
- if (ram_addr - block["offset"]) < block["used_length"]:
- return block
-
- raise gdb.GdbError("Bad ram offset %x" % ram_addr)
-
-
-def qemu_get_ram_ptr(ram_addr):
- """Returns qemu vaddr for given guest physical address."""
-
- block = qemu_get_ram_block(ram_addr)
- return block["host"] + (ram_addr - block["offset"])
-
-
-def memory_region_get_ram_ptr(memory_region):
- if memory_region["alias"] != 0:
- return (memory_region_get_ram_ptr(memory_region["alias"].dereference())
- + memory_region["alias_offset"])
-
- return qemu_get_ram_ptr(memory_region["ram_block"]["offset"])
-
-
-def get_guest_phys_blocks():
- """Returns a list of ram blocks.
-
- Each block entry contains:
- 'target_start': guest block phys start address
- 'target_end': guest block phys end address
- 'host_addr': qemu vaddr of the block's start
- """
-
- guest_phys_blocks = []
-
- print("guest RAM blocks:")
- print("target_start target_end host_addr message "
- "count")
- print("---------------- ---------------- ---------------- ------- "
- "-----")
-
- current_map_p = gdb.parse_and_eval("address_space_memory.current_map")
- current_map = current_map_p.dereference()
-
- # Conversion to int is needed for python 3
- # compatibility. Otherwise range doesn't cast the value itself and
- # breaks.
- for cur in range(int(current_map["nr"])):
- flat_range = (current_map["ranges"] + cur).dereference()
- memory_region = flat_range["mr"].dereference()
-
- # we only care about RAM
- if not memory_region["ram"]:
- continue
-
- section_size = int128_get64(flat_range["addr"]["size"])
- target_start = int128_get64(flat_range["addr"]["start"])
- target_end = target_start + section_size
- host_addr = (memory_region_get_ram_ptr(memory_region)
- + flat_range["offset_in_region"])
- predecessor = None
-
- # find continuity in guest physical address space
- if len(guest_phys_blocks) > 0:
- predecessor = guest_phys_blocks[-1]
- predecessor_size = (predecessor["target_end"] -
- predecessor["target_start"])
-
- # the memory API guarantees monotonically increasing
- # traversal
- assert predecessor["target_end"] <= target_start
-
- # we want continuity in both guest-physical and
- # host-virtual memory
- if (predecessor["target_end"] < target_start or
- predecessor["host_addr"] + predecessor_size != host_addr):
- predecessor = None
-
- if predecessor is None:
- # isolated mapping, add it to the list
- guest_phys_blocks.append({"target_start": target_start,
- "target_end": target_end,
- "host_addr": host_addr})
- message = "added"
- else:
- # expand predecessor until @target_end; predecessor's
- # start doesn't change
- predecessor["target_end"] = target_end
- message = "joined"
-
- print("%016x %016x %016x %-7s %5u" %
- (target_start, target_end, host_addr.cast(UINTPTR_T),
- message, len(guest_phys_blocks)))
-
- return guest_phys_blocks
-
-
-# The leading docstring doesn't have idiomatic Python formatting. It is
-# printed by gdb's "help" command (the first line is printed in the
-# "help data" summary), and it should match how other help texts look in
-# gdb.
-class DumpGuestMemory(gdb.Command):
- """Extract guest vmcore from qemu process coredump.
-
-The two required arguments are FILE and ARCH:
-FILE identifies the target file to write the guest vmcore to.
-ARCH specifies the architecture for which the core will be generated.
-
-This GDB command reimplements the dump-guest-memory QMP command in
-python, using the representation of guest memory as captured in the qemu
-coredump. The qemu process that has been dumped must have had the
-command line option "-machine dump-guest-core=on" which is the default.
-
-For simplicity, the "paging", "begin" and "end" parameters of the QMP
-command are not supported -- no attempt is made to get the guest's
-internal paging structures (ie. paging=false is hard-wired), and guest
-memory is always fully dumped.
-
-Currently aarch64-be, aarch64-le, X86_64, 386, s390, ppc64-be,
-ppc64-le guests are supported.
-
-The CORE/NT_PRSTATUS and QEMU notes (that is, the VCPUs' statuses) are
-not written to the vmcore. Preparing these would require context that is
-only present in the KVM host kernel module when the guest is alive. A
-fake ELF note is written instead, only to keep the ELF parser of "crash"
-happy.
-
-Dependent on how busted the qemu process was at the time of the
-coredump, this command might produce unpredictable results. If qemu
-deliberately called abort(), or it was dumped in response to a signal at
-a halfway fortunate point, then its coredump should be in reasonable
-shape and this command should mostly work."""
-
- def __init__(self):
- super(DumpGuestMemory, self).__init__("dump-guest-memory",
- gdb.COMMAND_DATA,
- gdb.COMPLETE_FILENAME)
- self.elf = None
- self.guest_phys_blocks = None
-
- def dump_init(self, vmcore):
- """Prepares and writes ELF structures to core file."""
-
- # Needed to make crash happy, data for more useful notes is
- # not available in a qemu core.
- self.elf.add_note("NONE", "EMPTY", 0)
-
- # We should never reach PN_XNUM for paging=false dumps,
- # there's just a handful of discontiguous ranges after
- # merging.
- # The constant is needed to account for the PT_NOTE segment.
- phdr_num = len(self.guest_phys_blocks) + 1
- assert phdr_num < PN_XNUM
-
- for block in self.guest_phys_blocks:
- block_size = block["target_end"] - block["target_start"]
- self.elf.add_segment(PT_LOAD, block["target_start"], block_size)
-
- self.elf.to_file(vmcore)
-
- def dump_iterate(self, vmcore):
- """Writes guest core to file."""
-
- qemu_core = gdb.inferiors()[0]
- for block in self.guest_phys_blocks:
- cur = block["host_addr"]
- left = block["target_end"] - block["target_start"]
- print("dumping range at %016x for length %016x" %
- (cur.cast(UINTPTR_T), left))
-
- while left > 0:
- chunk_size = min(TARGET_PAGE_SIZE, left)
- chunk = qemu_core.read_memory(cur, chunk_size)
- vmcore.write(chunk)
- cur += chunk_size
- left -= chunk_size
-
- def invoke(self, args, from_tty):
- """Handles command invocation from gdb."""
-
- # Unwittingly pressing the Enter key after the command should
- # not dump the same multi-gig coredump to the same file.
- self.dont_repeat()
-
- argv = gdb.string_to_argv(args)
- if len(argv) != 2:
- raise gdb.GdbError("usage: dump-guest-memory FILE ARCH")
-
- self.elf = ELF(argv[1])
- self.guest_phys_blocks = get_guest_phys_blocks()
-
- with open(argv[0], "wb") as vmcore:
- self.dump_init(vmcore)
- self.dump_iterate(vmcore)
-
-DumpGuestMemory()