|  | """ | 
|  | 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 | 
|  | import struct | 
|  |  | 
|  | try: | 
|  | UINTPTR_T = gdb.lookup_type("uintptr_t") | 
|  | except Exception as inst: | 
|  | raise gdb.GdbError("Symbols must be loaded prior to sourcing dump-guest-memory.\n" | 
|  | "Symbols may be loaded by 'attach'ing a QEMU process id or by " | 
|  | "'load'ing a QEMU binary.") | 
|  |  | 
|  | 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 | 
|  |  | 
|  | VMCOREINFO_FORMAT_ELF = 1 | 
|  |  | 
|  | def le16_to_cpu(val): | 
|  | return struct.unpack("<H", struct.pack("=H", val))[0] | 
|  |  | 
|  | def le32_to_cpu(val): | 
|  | return struct.unpack("<I", struct.pack("=I", val))[0] | 
|  |  | 
|  | def le64_to_cpu(val): | 
|  | return struct.unpack("<Q", struct.pack("=Q", val))[0] | 
|  |  | 
|  | class ELF(object): | 
|  | """Representation of a ELF file.""" | 
|  |  | 
|  | def __init__(self, arch): | 
|  | self.ehdr = None | 
|  | self.notes = [] | 
|  | self.segments = [] | 
|  | self.notes_size = 0 | 
|  | self.endianness = None | 
|  | self.elfclass = ELFCLASS64 | 
|  |  | 
|  | if arch == 'aarch64-le': | 
|  | self.endianness = ELFDATA2LSB | 
|  | self.elfclass = ELFCLASS64 | 
|  | self.ehdr = get_arch_ehdr(self.endianness, self.elfclass) | 
|  | self.ehdr.e_machine = EM_AARCH | 
|  |  | 
|  | elif arch == 'aarch64-be': | 
|  | self.endianness = ELFDATA2MSB | 
|  | self.ehdr = get_arch_ehdr(self.endianness, self.elfclass) | 
|  | self.ehdr.e_machine = EM_AARCH | 
|  |  | 
|  | elif arch == 'X86_64': | 
|  | self.endianness = ELFDATA2LSB | 
|  | self.ehdr = get_arch_ehdr(self.endianness, self.elfclass) | 
|  | self.ehdr.e_machine = EM_X86_64 | 
|  |  | 
|  | elif arch == '386': | 
|  | self.endianness = ELFDATA2LSB | 
|  | self.elfclass = ELFCLASS32 | 
|  | self.ehdr = get_arch_ehdr(self.endianness, self.elfclass) | 
|  | self.ehdr.e_machine = EM_386 | 
|  |  | 
|  | elif arch == 's390': | 
|  | self.endianness = ELFDATA2MSB | 
|  | self.ehdr = get_arch_ehdr(self.endianness, self.elfclass) | 
|  | self.ehdr.e_machine = EM_S390 | 
|  |  | 
|  | elif arch == 'ppc64-le': | 
|  | self.endianness = ELFDATA2LSB | 
|  | self.ehdr = get_arch_ehdr(self.endianness, self.elfclass) | 
|  | self.ehdr.e_machine = EM_PPC64 | 
|  |  | 
|  | elif arch == 'ppc64-be': | 
|  | self.endianness = ELFDATA2MSB | 
|  | self.ehdr = get_arch_ehdr(self.endianness, 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.endianness, 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_vmcoreinfo_note(self, vmcoreinfo): | 
|  | """Adds a vmcoreinfo note to the ELF dump.""" | 
|  | # compute the header size, and copy that many bytes from the note | 
|  | header = get_arch_note(self.endianness, 0, 0) | 
|  | ctypes.memmove(ctypes.pointer(header), | 
|  | vmcoreinfo, ctypes.sizeof(header)) | 
|  | if header.n_descsz > 1 << 20: | 
|  | print('warning: invalid vmcoreinfo size') | 
|  | return | 
|  | # now get the full note | 
|  | note = get_arch_note(self.endianness, | 
|  | header.n_namesz - 1, header.n_descsz) | 
|  | ctypes.memmove(ctypes.pointer(note), vmcoreinfo, ctypes.sizeof(note)) | 
|  |  | 
|  | 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.endianness, self.elfclass) | 
|  | phdr.p_type = p_type | 
|  | phdr.p_paddr = p_paddr | 
|  | phdr.p_vaddr = 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 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(endianness, len_name, len_desc): | 
|  | """Returns a Note class with the specified endianness.""" | 
|  |  | 
|  | if endianness == 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, endianness, 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 = endianness | 
|  | self.ei_version = EV_CURRENT | 
|  |  | 
|  |  | 
|  | def get_arch_ehdr(endianness, elfclass): | 
|  | """Returns a EHDR64 class with the specified endianness.""" | 
|  |  | 
|  | if endianness == 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(endianness, 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(endianness, 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(endianness, 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(endianness, elfclass)) | 
|  | self.e_phnum = 0 | 
|  |  | 
|  | # End get_arch_ehdr | 
|  | if elfclass == ELFCLASS64: | 
|  | return EHDR64() | 
|  | else: | 
|  | return EHDR32() | 
|  |  | 
|  |  | 
|  | def get_arch_phdr(endianness, elfclass): | 
|  | """Returns a 32 or 64 bit PHDR class with the specified endianness.""" | 
|  |  | 
|  | if endianness == 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.""" | 
|  |  | 
|  | try: | 
|  | assert val["hi"] == 0 | 
|  | return val["lo"] | 
|  | except gdb.error: | 
|  | u64t = gdb.lookup_type('uint64_t').array(2) | 
|  | u64 = val.cast(u64t) | 
|  | if sys.byteorder == 'little': | 
|  | assert u64[1] == 0 | 
|  | return u64[0] | 
|  | else: | 
|  | assert u64[0] == 0 | 
|  | return u64[1] | 
|  |  | 
|  |  | 
|  | 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_map_ram_ptr(block, offset): | 
|  | """Returns qemu vaddr for given guest physical address.""" | 
|  |  | 
|  | return block["host"] + 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_map_ram_ptr(memory_region["ram_block"], 0) | 
|  |  | 
|  |  | 
|  | 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"] or | 
|  | memory_region["ram_device"] or | 
|  | memory_region["nonvolatile"]): | 
|  | 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 phys_memory_read(self, addr, size): | 
|  | qemu_core = gdb.inferiors()[0] | 
|  | for block in self.guest_phys_blocks: | 
|  | if block["target_start"] <= addr \ | 
|  | and addr + size <= block["target_end"]: | 
|  | haddr = block["host_addr"] + (addr - block["target_start"]) | 
|  | return qemu_core.read_memory(haddr, size) | 
|  | return None | 
|  |  | 
|  | def add_vmcoreinfo(self): | 
|  | if gdb.lookup_symbol("vmcoreinfo_realize")[0] is None: | 
|  | return | 
|  | vmci = 'vmcoreinfo_realize::vmcoreinfo_state' | 
|  | if not gdb.parse_and_eval("%s" % vmci) \ | 
|  | or not gdb.parse_and_eval("(%s)->has_vmcoreinfo" % vmci): | 
|  | return | 
|  |  | 
|  | fmt = gdb.parse_and_eval("(%s)->vmcoreinfo.guest_format" % vmci) | 
|  | addr = gdb.parse_and_eval("(%s)->vmcoreinfo.paddr" % vmci) | 
|  | size = gdb.parse_and_eval("(%s)->vmcoreinfo.size" % vmci) | 
|  |  | 
|  | fmt = le16_to_cpu(fmt) | 
|  | addr = le64_to_cpu(addr) | 
|  | size = le32_to_cpu(size) | 
|  |  | 
|  | if fmt != VMCOREINFO_FORMAT_ELF: | 
|  | return | 
|  |  | 
|  | vmcoreinfo = self.phys_memory_read(addr, size) | 
|  | if vmcoreinfo: | 
|  | self.elf.add_vmcoreinfo_note(bytes(vmcoreinfo)) | 
|  |  | 
|  | 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() | 
|  | self.add_vmcoreinfo() | 
|  |  | 
|  | with open(argv[0], "wb") as vmcore: | 
|  | self.dump_init(vmcore) | 
|  | self.dump_iterate(vmcore) | 
|  |  | 
|  | DumpGuestMemory() |