xref: /openbmc/qemu/scripts/dump-guest-memory.py (revision 438c78da)
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