xref: /openbmc/qemu/scripts/dump-guest-memory.py (revision 20a4f14f)
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"""
15
16import ctypes
17import struct
18
19try:
20    UINTPTR_T = gdb.lookup_type("uintptr_t")
21except Exception as inst:
22    raise gdb.GdbError("Symbols must be loaded prior to sourcing dump-guest-memory.\n"
23                       "Symbols may be loaded by 'attach'ing a QEMU process id or by "
24                       "'load'ing a QEMU binary.")
25
26TARGET_PAGE_SIZE = 0x1000
27TARGET_PAGE_MASK = 0xFFFFFFFFFFFFF000
28
29# Special value for e_phnum. This indicates that the real number of
30# program headers is too large to fit into e_phnum. Instead the real
31# value is in the field sh_info of section 0.
32PN_XNUM = 0xFFFF
33
34EV_CURRENT = 1
35
36ELFCLASS32 = 1
37ELFCLASS64 = 2
38
39ELFDATA2LSB = 1
40ELFDATA2MSB = 2
41
42ET_CORE = 4
43
44PT_LOAD = 1
45PT_NOTE = 4
46
47EM_386 = 3
48EM_PPC = 20
49EM_PPC64 = 21
50EM_S390 = 22
51EM_AARCH = 183
52EM_X86_64 = 62
53
54VMCOREINFO_FORMAT_ELF = 1
55
56def le16_to_cpu(val):
57    return struct.unpack("<H", struct.pack("=H", val))[0]
58
59def le32_to_cpu(val):
60    return struct.unpack("<I", struct.pack("=I", val))[0]
61
62def le64_to_cpu(val):
63    return struct.unpack("<Q", struct.pack("=Q", val))[0]
64
65class ELF(object):
66    """Representation of a ELF file."""
67
68    def __init__(self, arch):
69        self.ehdr = None
70        self.notes = []
71        self.segments = []
72        self.notes_size = 0
73        self.endianness = None
74        self.elfclass = ELFCLASS64
75
76        if arch == 'aarch64-le':
77            self.endianness = ELFDATA2LSB
78            self.elfclass = ELFCLASS64
79            self.ehdr = get_arch_ehdr(self.endianness, self.elfclass)
80            self.ehdr.e_machine = EM_AARCH
81
82        elif arch == 'aarch64-be':
83            self.endianness = ELFDATA2MSB
84            self.ehdr = get_arch_ehdr(self.endianness, self.elfclass)
85            self.ehdr.e_machine = EM_AARCH
86
87        elif arch == 'X86_64':
88            self.endianness = ELFDATA2LSB
89            self.ehdr = get_arch_ehdr(self.endianness, self.elfclass)
90            self.ehdr.e_machine = EM_X86_64
91
92        elif arch == '386':
93            self.endianness = ELFDATA2LSB
94            self.elfclass = ELFCLASS32
95            self.ehdr = get_arch_ehdr(self.endianness, self.elfclass)
96            self.ehdr.e_machine = EM_386
97
98        elif arch == 's390':
99            self.endianness = ELFDATA2MSB
100            self.ehdr = get_arch_ehdr(self.endianness, self.elfclass)
101            self.ehdr.e_machine = EM_S390
102
103        elif arch == 'ppc64-le':
104            self.endianness = ELFDATA2LSB
105            self.ehdr = get_arch_ehdr(self.endianness, self.elfclass)
106            self.ehdr.e_machine = EM_PPC64
107
108        elif arch == 'ppc64-be':
109            self.endianness = ELFDATA2MSB
110            self.ehdr = get_arch_ehdr(self.endianness, self.elfclass)
111            self.ehdr.e_machine = EM_PPC64
112
113        else:
114            raise gdb.GdbError("No valid arch type specified.\n"
115                               "Currently supported types:\n"
116                               "aarch64-be, aarch64-le, X86_64, 386, s390, "
117                               "ppc64-be, ppc64-le")
118
119        self.add_segment(PT_NOTE, 0, 0)
120
121    def add_note(self, n_name, n_desc, n_type):
122        """Adds a note to the ELF."""
123
124        note = get_arch_note(self.endianness, len(n_name), len(n_desc))
125        note.n_namesz = len(n_name) + 1
126        note.n_descsz = len(n_desc)
127        note.n_name = n_name.encode()
128        note.n_type = n_type
129
130        # Desc needs to be 4 byte aligned (although the 64bit spec
131        # specifies 8 byte). When defining n_desc as uint32 it will be
132        # automatically aligned but we need the memmove to copy the
133        # string into it.
134        ctypes.memmove(note.n_desc, n_desc.encode(), len(n_desc))
135
136        self.notes.append(note)
137        self.segments[0].p_filesz += ctypes.sizeof(note)
138        self.segments[0].p_memsz += ctypes.sizeof(note)
139
140
141    def add_vmcoreinfo_note(self, vmcoreinfo):
142        """Adds a vmcoreinfo note to the ELF dump."""
143        # compute the header size, and copy that many bytes from the note
144        header = get_arch_note(self.endianness, 0, 0)
145        ctypes.memmove(ctypes.pointer(header),
146                       vmcoreinfo, ctypes.sizeof(header))
147        if header.n_descsz > 1 << 20:
148            print('warning: invalid vmcoreinfo size')
149            return
150        # now get the full note
151        note = get_arch_note(self.endianness,
152                             header.n_namesz - 1, header.n_descsz)
153        ctypes.memmove(ctypes.pointer(note), vmcoreinfo, ctypes.sizeof(note))
154
155        self.notes.append(note)
156        self.segments[0].p_filesz += ctypes.sizeof(note)
157        self.segments[0].p_memsz += ctypes.sizeof(note)
158
159    def add_segment(self, p_type, p_paddr, p_size):
160        """Adds a segment to the elf."""
161
162        phdr = get_arch_phdr(self.endianness, self.elfclass)
163        phdr.p_type = p_type
164        phdr.p_paddr = p_paddr
165        phdr.p_vaddr = 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 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"] or
421            memory_region["ram_device"] or
422            memory_region["nonvolatile"]):
423            continue
424
425        section_size = int128_get64(flat_range["addr"]["size"])
426        target_start = int128_get64(flat_range["addr"]["start"])
427        target_end = target_start + section_size
428        host_addr = (memory_region_get_ram_ptr(memory_region)
429                     + flat_range["offset_in_region"])
430        predecessor = None
431
432        # find continuity in guest physical address space
433        if len(guest_phys_blocks) > 0:
434            predecessor = guest_phys_blocks[-1]
435            predecessor_size = (predecessor["target_end"] -
436                                predecessor["target_start"])
437
438            # the memory API guarantees monotonically increasing
439            # traversal
440            assert predecessor["target_end"] <= target_start
441
442            # we want continuity in both guest-physical and
443            # host-virtual memory
444            if (predecessor["target_end"] < target_start or
445                predecessor["host_addr"] + predecessor_size != host_addr):
446                predecessor = None
447
448        if predecessor is None:
449            # isolated mapping, add it to the list
450            guest_phys_blocks.append({"target_start": target_start,
451                                      "target_end":   target_end,
452                                      "host_addr":    host_addr})
453            message = "added"
454        else:
455            # expand predecessor until @target_end; predecessor's
456            # start doesn't change
457            predecessor["target_end"] = target_end
458            message = "joined"
459
460        print("%016x %016x %016x %-7s %5u" %
461              (target_start, target_end, host_addr.cast(UINTPTR_T),
462               message, len(guest_phys_blocks)))
463
464    return guest_phys_blocks
465
466
467# The leading docstring doesn't have idiomatic Python formatting. It is
468# printed by gdb's "help" command (the first line is printed in the
469# "help data" summary), and it should match how other help texts look in
470# gdb.
471class DumpGuestMemory(gdb.Command):
472    """Extract guest vmcore from qemu process coredump.
473
474The two required arguments are FILE and ARCH:
475FILE identifies the target file to write the guest vmcore to.
476ARCH specifies the architecture for which the core will be generated.
477
478This GDB command reimplements the dump-guest-memory QMP command in
479python, using the representation of guest memory as captured in the qemu
480coredump. The qemu process that has been dumped must have had the
481command line option "-machine dump-guest-core=on" which is the default.
482
483For simplicity, the "paging", "begin" and "end" parameters of the QMP
484command are not supported -- no attempt is made to get the guest's
485internal paging structures (ie. paging=false is hard-wired), and guest
486memory is always fully dumped.
487
488Currently aarch64-be, aarch64-le, X86_64, 386, s390, ppc64-be,
489ppc64-le guests are supported.
490
491The CORE/NT_PRSTATUS and QEMU notes (that is, the VCPUs' statuses) are
492not written to the vmcore. Preparing these would require context that is
493only present in the KVM host kernel module when the guest is alive. A
494fake ELF note is written instead, only to keep the ELF parser of "crash"
495happy.
496
497Dependent on how busted the qemu process was at the time of the
498coredump, this command might produce unpredictable results. If qemu
499deliberately called abort(), or it was dumped in response to a signal at
500a halfway fortunate point, then its coredump should be in reasonable
501shape and this command should mostly work."""
502
503    def __init__(self):
504        super(DumpGuestMemory, self).__init__("dump-guest-memory",
505                                              gdb.COMMAND_DATA,
506                                              gdb.COMPLETE_FILENAME)
507        self.elf = None
508        self.guest_phys_blocks = None
509
510    def dump_init(self, vmcore):
511        """Prepares and writes ELF structures to core file."""
512
513        # Needed to make crash happy, data for more useful notes is
514        # not available in a qemu core.
515        self.elf.add_note("NONE", "EMPTY", 0)
516
517        # We should never reach PN_XNUM for paging=false dumps,
518        # there's just a handful of discontiguous ranges after
519        # merging.
520        # The constant is needed to account for the PT_NOTE segment.
521        phdr_num = len(self.guest_phys_blocks) + 1
522        assert phdr_num < PN_XNUM
523
524        for block in self.guest_phys_blocks:
525            block_size = block["target_end"] - block["target_start"]
526            self.elf.add_segment(PT_LOAD, block["target_start"], block_size)
527
528        self.elf.to_file(vmcore)
529
530    def dump_iterate(self, vmcore):
531        """Writes guest core to file."""
532
533        qemu_core = gdb.inferiors()[0]
534        for block in self.guest_phys_blocks:
535            cur = block["host_addr"]
536            left = block["target_end"] - block["target_start"]
537            print("dumping range at %016x for length %016x" %
538                  (cur.cast(UINTPTR_T), left))
539
540            while left > 0:
541                chunk_size = min(TARGET_PAGE_SIZE, left)
542                chunk = qemu_core.read_memory(cur, chunk_size)
543                vmcore.write(chunk)
544                cur += chunk_size
545                left -= chunk_size
546
547    def phys_memory_read(self, addr, size):
548        qemu_core = gdb.inferiors()[0]
549        for block in self.guest_phys_blocks:
550            if block["target_start"] <= addr \
551               and addr + size <= block["target_end"]:
552                haddr = block["host_addr"] + (addr - block["target_start"])
553                return qemu_core.read_memory(haddr, size)
554        return None
555
556    def add_vmcoreinfo(self):
557        if gdb.lookup_symbol("vmcoreinfo_realize")[0] is None:
558            return
559        vmci = 'vmcoreinfo_realize::vmcoreinfo_state'
560        if not gdb.parse_and_eval("%s" % vmci) \
561           or not gdb.parse_and_eval("(%s)->has_vmcoreinfo" % vmci):
562            return
563
564        fmt = gdb.parse_and_eval("(%s)->vmcoreinfo.guest_format" % vmci)
565        addr = gdb.parse_and_eval("(%s)->vmcoreinfo.paddr" % vmci)
566        size = gdb.parse_and_eval("(%s)->vmcoreinfo.size" % vmci)
567
568        fmt = le16_to_cpu(fmt)
569        addr = le64_to_cpu(addr)
570        size = le32_to_cpu(size)
571
572        if fmt != VMCOREINFO_FORMAT_ELF:
573            return
574
575        vmcoreinfo = self.phys_memory_read(addr, size)
576        if vmcoreinfo:
577            self.elf.add_vmcoreinfo_note(bytes(vmcoreinfo))
578
579    def invoke(self, args, from_tty):
580        """Handles command invocation from gdb."""
581
582        # Unwittingly pressing the Enter key after the command should
583        # not dump the same multi-gig coredump to the same file.
584        self.dont_repeat()
585
586        argv = gdb.string_to_argv(args)
587        if len(argv) != 2:
588            raise gdb.GdbError("usage: dump-guest-memory FILE ARCH")
589
590        self.elf = ELF(argv[1])
591        self.guest_phys_blocks = get_guest_phys_blocks()
592        self.add_vmcoreinfo()
593
594        with open(argv[0], "wb") as vmcore:
595            self.dump_init(vmcore)
596            self.dump_iterate(vmcore)
597
598DumpGuestMemory()
599