xref: /openbmc/qemu/scripts/dump-guest-memory.py (revision 3d9569b8)
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_vaddr = p_paddr
167        phdr.p_filesz = p_size
168        phdr.p_memsz = p_size
169        self.segments.append(phdr)
170        self.ehdr.e_phnum += 1
171
172    def to_file(self, elf_file):
173        """Writes all ELF structures to the the passed file.
174
175        Structure:
176        Ehdr
177        Segment 0:PT_NOTE
178        Segment 1:PT_LOAD
179        Segment N:PT_LOAD
180        Note    0..N
181        Dump contents
182        """
183        elf_file.write(self.ehdr)
184        off = ctypes.sizeof(self.ehdr) + \
185              len(self.segments) * ctypes.sizeof(self.segments[0])
186
187        for phdr in self.segments:
188            phdr.p_offset = off
189            elf_file.write(phdr)
190            off += phdr.p_filesz
191
192        for note in self.notes:
193            elf_file.write(note)
194
195
196def get_arch_note(endianness, len_name, len_desc):
197    """Returns a Note class with the specified endianness."""
198
199    if endianness == ELFDATA2LSB:
200        superclass = ctypes.LittleEndianStructure
201    else:
202        superclass = ctypes.BigEndianStructure
203
204    len_name = len_name + 1
205
206    class Note(superclass):
207        """Represents an ELF note, includes the content."""
208
209        _fields_ = [("n_namesz", ctypes.c_uint32),
210                    ("n_descsz", ctypes.c_uint32),
211                    ("n_type", ctypes.c_uint32),
212                    ("n_name", ctypes.c_char * len_name),
213                    ("n_desc", ctypes.c_uint32 * ((len_desc + 3) // 4))]
214    return Note()
215
216
217class Ident(ctypes.Structure):
218    """Represents the ELF ident array in the ehdr structure."""
219
220    _fields_ = [('ei_mag0', ctypes.c_ubyte),
221                ('ei_mag1', ctypes.c_ubyte),
222                ('ei_mag2', ctypes.c_ubyte),
223                ('ei_mag3', ctypes.c_ubyte),
224                ('ei_class', ctypes.c_ubyte),
225                ('ei_data', ctypes.c_ubyte),
226                ('ei_version', ctypes.c_ubyte),
227                ('ei_osabi', ctypes.c_ubyte),
228                ('ei_abiversion', ctypes.c_ubyte),
229                ('ei_pad', ctypes.c_ubyte * 7)]
230
231    def __init__(self, endianness, elfclass):
232        self.ei_mag0 = 0x7F
233        self.ei_mag1 = ord('E')
234        self.ei_mag2 = ord('L')
235        self.ei_mag3 = ord('F')
236        self.ei_class = elfclass
237        self.ei_data = endianness
238        self.ei_version = EV_CURRENT
239
240
241def get_arch_ehdr(endianness, elfclass):
242    """Returns a EHDR64 class with the specified endianness."""
243
244    if endianness == ELFDATA2LSB:
245        superclass = ctypes.LittleEndianStructure
246    else:
247        superclass = ctypes.BigEndianStructure
248
249    class EHDR64(superclass):
250        """Represents the 64 bit ELF header struct."""
251
252        _fields_ = [('e_ident', Ident),
253                    ('e_type', ctypes.c_uint16),
254                    ('e_machine', ctypes.c_uint16),
255                    ('e_version', ctypes.c_uint32),
256                    ('e_entry', ctypes.c_uint64),
257                    ('e_phoff', ctypes.c_uint64),
258                    ('e_shoff', ctypes.c_uint64),
259                    ('e_flags', ctypes.c_uint32),
260                    ('e_ehsize', ctypes.c_uint16),
261                    ('e_phentsize', ctypes.c_uint16),
262                    ('e_phnum', ctypes.c_uint16),
263                    ('e_shentsize', ctypes.c_uint16),
264                    ('e_shnum', ctypes.c_uint16),
265                    ('e_shstrndx', ctypes.c_uint16)]
266
267        def __init__(self):
268            super(superclass, self).__init__()
269            self.e_ident = Ident(endianness, elfclass)
270            self.e_type = ET_CORE
271            self.e_version = EV_CURRENT
272            self.e_ehsize = ctypes.sizeof(self)
273            self.e_phoff = ctypes.sizeof(self)
274            self.e_phentsize = ctypes.sizeof(get_arch_phdr(endianness, elfclass))
275            self.e_phnum = 0
276
277
278    class EHDR32(superclass):
279        """Represents the 32 bit ELF header struct."""
280
281        _fields_ = [('e_ident', Ident),
282                    ('e_type', ctypes.c_uint16),
283                    ('e_machine', ctypes.c_uint16),
284                    ('e_version', ctypes.c_uint32),
285                    ('e_entry', ctypes.c_uint32),
286                    ('e_phoff', ctypes.c_uint32),
287                    ('e_shoff', ctypes.c_uint32),
288                    ('e_flags', ctypes.c_uint32),
289                    ('e_ehsize', ctypes.c_uint16),
290                    ('e_phentsize', ctypes.c_uint16),
291                    ('e_phnum', ctypes.c_uint16),
292                    ('e_shentsize', ctypes.c_uint16),
293                    ('e_shnum', ctypes.c_uint16),
294                    ('e_shstrndx', ctypes.c_uint16)]
295
296        def __init__(self):
297            super(superclass, self).__init__()
298            self.e_ident = Ident(endianness, elfclass)
299            self.e_type = ET_CORE
300            self.e_version = EV_CURRENT
301            self.e_ehsize = ctypes.sizeof(self)
302            self.e_phoff = ctypes.sizeof(self)
303            self.e_phentsize = ctypes.sizeof(get_arch_phdr(endianness, elfclass))
304            self.e_phnum = 0
305
306    # End get_arch_ehdr
307    if elfclass == ELFCLASS64:
308        return EHDR64()
309    else:
310        return EHDR32()
311
312
313def get_arch_phdr(endianness, elfclass):
314    """Returns a 32 or 64 bit PHDR class with the specified endianness."""
315
316    if endianness == ELFDATA2LSB:
317        superclass = ctypes.LittleEndianStructure
318    else:
319        superclass = ctypes.BigEndianStructure
320
321    class PHDR64(superclass):
322        """Represents the 64 bit ELF program header struct."""
323
324        _fields_ = [('p_type', ctypes.c_uint32),
325                    ('p_flags', ctypes.c_uint32),
326                    ('p_offset', ctypes.c_uint64),
327                    ('p_vaddr', ctypes.c_uint64),
328                    ('p_paddr', ctypes.c_uint64),
329                    ('p_filesz', ctypes.c_uint64),
330                    ('p_memsz', ctypes.c_uint64),
331                    ('p_align', ctypes.c_uint64)]
332
333    class PHDR32(superclass):
334        """Represents the 32 bit ELF program header struct."""
335
336        _fields_ = [('p_type', ctypes.c_uint32),
337                    ('p_offset', ctypes.c_uint32),
338                    ('p_vaddr', ctypes.c_uint32),
339                    ('p_paddr', ctypes.c_uint32),
340                    ('p_filesz', ctypes.c_uint32),
341                    ('p_memsz', ctypes.c_uint32),
342                    ('p_flags', ctypes.c_uint32),
343                    ('p_align', ctypes.c_uint32)]
344
345    # End get_arch_phdr
346    if elfclass == ELFCLASS64:
347        return PHDR64()
348    else:
349        return PHDR32()
350
351
352def int128_get64(val):
353    """Returns low 64bit part of Int128 struct."""
354
355    try:
356        assert val["hi"] == 0
357        return val["lo"]
358    except gdb.error:
359        u64t = gdb.lookup_type('uint64_t').array(2)
360        u64 = val.cast(u64t)
361        if sys.byteorder == 'little':
362            assert u64[1] == 0
363            return u64[0]
364        else:
365            assert u64[0] == 0
366            return u64[1]
367
368
369def qlist_foreach(head, field_str):
370    """Generator for qlists."""
371
372    var_p = head["lh_first"]
373    while var_p != 0:
374        var = var_p.dereference()
375        var_p = var[field_str]["le_next"]
376        yield var
377
378
379def qemu_map_ram_ptr(block, offset):
380    """Returns qemu vaddr for given guest physical address."""
381
382    return block["host"] + offset
383
384
385def memory_region_get_ram_ptr(memory_region):
386    if memory_region["alias"] != 0:
387        return (memory_region_get_ram_ptr(memory_region["alias"].dereference())
388                + memory_region["alias_offset"])
389
390    return qemu_map_ram_ptr(memory_region["ram_block"], 0)
391
392
393def get_guest_phys_blocks():
394    """Returns a list of ram blocks.
395
396    Each block entry contains:
397    'target_start': guest block phys start address
398    'target_end':   guest block phys end address
399    'host_addr':    qemu vaddr of the block's start
400    """
401
402    guest_phys_blocks = []
403
404    print("guest RAM blocks:")
405    print("target_start     target_end       host_addr        message "
406          "count")
407    print("---------------- ---------------- ---------------- ------- "
408          "-----")
409
410    current_map_p = gdb.parse_and_eval("address_space_memory.current_map")
411    current_map = current_map_p.dereference()
412
413    # Conversion to int is needed for python 3
414    # compatibility. Otherwise range doesn't cast the value itself and
415    # breaks.
416    for cur in range(int(current_map["nr"])):
417        flat_range = (current_map["ranges"] + cur).dereference()
418        memory_region = flat_range["mr"].dereference()
419
420        # we only care about RAM
421        if (not memory_region["ram"] or
422            memory_region["ram_device"] or
423            memory_region["nonvolatile"]):
424            continue
425
426        section_size = int128_get64(flat_range["addr"]["size"])
427        target_start = int128_get64(flat_range["addr"]["start"])
428        target_end = target_start + section_size
429        host_addr = (memory_region_get_ram_ptr(memory_region)
430                     + flat_range["offset_in_region"])
431        predecessor = None
432
433        # find continuity in guest physical address space
434        if len(guest_phys_blocks) > 0:
435            predecessor = guest_phys_blocks[-1]
436            predecessor_size = (predecessor["target_end"] -
437                                predecessor["target_start"])
438
439            # the memory API guarantees monotonically increasing
440            # traversal
441            assert predecessor["target_end"] <= target_start
442
443            # we want continuity in both guest-physical and
444            # host-virtual memory
445            if (predecessor["target_end"] < target_start or
446                predecessor["host_addr"] + predecessor_size != host_addr):
447                predecessor = None
448
449        if predecessor is None:
450            # isolated mapping, add it to the list
451            guest_phys_blocks.append({"target_start": target_start,
452                                      "target_end":   target_end,
453                                      "host_addr":    host_addr})
454            message = "added"
455        else:
456            # expand predecessor until @target_end; predecessor's
457            # start doesn't change
458            predecessor["target_end"] = target_end
459            message = "joined"
460
461        print("%016x %016x %016x %-7s %5u" %
462              (target_start, target_end, host_addr.cast(UINTPTR_T),
463               message, len(guest_phys_blocks)))
464
465    return guest_phys_blocks
466
467
468# The leading docstring doesn't have idiomatic Python formatting. It is
469# printed by gdb's "help" command (the first line is printed in the
470# "help data" summary), and it should match how other help texts look in
471# gdb.
472class DumpGuestMemory(gdb.Command):
473    """Extract guest vmcore from qemu process coredump.
474
475The two required arguments are FILE and ARCH:
476FILE identifies the target file to write the guest vmcore to.
477ARCH specifies the architecture for which the core will be generated.
478
479This GDB command reimplements the dump-guest-memory QMP command in
480python, using the representation of guest memory as captured in the qemu
481coredump. The qemu process that has been dumped must have had the
482command line option "-machine dump-guest-core=on" which is the default.
483
484For simplicity, the "paging", "begin" and "end" parameters of the QMP
485command are not supported -- no attempt is made to get the guest's
486internal paging structures (ie. paging=false is hard-wired), and guest
487memory is always fully dumped.
488
489Currently aarch64-be, aarch64-le, X86_64, 386, s390, ppc64-be,
490ppc64-le guests are supported.
491
492The CORE/NT_PRSTATUS and QEMU notes (that is, the VCPUs' statuses) are
493not written to the vmcore. Preparing these would require context that is
494only present in the KVM host kernel module when the guest is alive. A
495fake ELF note is written instead, only to keep the ELF parser of "crash"
496happy.
497
498Dependent on how busted the qemu process was at the time of the
499coredump, this command might produce unpredictable results. If qemu
500deliberately called abort(), or it was dumped in response to a signal at
501a halfway fortunate point, then its coredump should be in reasonable
502shape and this command should mostly work."""
503
504    def __init__(self):
505        super(DumpGuestMemory, self).__init__("dump-guest-memory",
506                                              gdb.COMMAND_DATA,
507                                              gdb.COMPLETE_FILENAME)
508        self.elf = None
509        self.guest_phys_blocks = None
510
511    def dump_init(self, vmcore):
512        """Prepares and writes ELF structures to core file."""
513
514        # Needed to make crash happy, data for more useful notes is
515        # not available in a qemu core.
516        self.elf.add_note("NONE", "EMPTY", 0)
517
518        # We should never reach PN_XNUM for paging=false dumps,
519        # there's just a handful of discontiguous ranges after
520        # merging.
521        # The constant is needed to account for the PT_NOTE segment.
522        phdr_num = len(self.guest_phys_blocks) + 1
523        assert phdr_num < PN_XNUM
524
525        for block in self.guest_phys_blocks:
526            block_size = block["target_end"] - block["target_start"]
527            self.elf.add_segment(PT_LOAD, block["target_start"], block_size)
528
529        self.elf.to_file(vmcore)
530
531    def dump_iterate(self, vmcore):
532        """Writes guest core to file."""
533
534        qemu_core = gdb.inferiors()[0]
535        for block in self.guest_phys_blocks:
536            cur = block["host_addr"]
537            left = block["target_end"] - block["target_start"]
538            print("dumping range at %016x for length %016x" %
539                  (cur.cast(UINTPTR_T), left))
540
541            while left > 0:
542                chunk_size = min(TARGET_PAGE_SIZE, left)
543                chunk = qemu_core.read_memory(cur, chunk_size)
544                vmcore.write(chunk)
545                cur += chunk_size
546                left -= chunk_size
547
548    def phys_memory_read(self, addr, size):
549        qemu_core = gdb.inferiors()[0]
550        for block in self.guest_phys_blocks:
551            if block["target_start"] <= addr \
552               and addr + size <= block["target_end"]:
553                haddr = block["host_addr"] + (addr - block["target_start"])
554                return qemu_core.read_memory(haddr, size)
555        return None
556
557    def add_vmcoreinfo(self):
558        if gdb.lookup_symbol("vmcoreinfo_realize")[0] is None:
559            return
560        vmci = 'vmcoreinfo_realize::vmcoreinfo_state'
561        if not gdb.parse_and_eval("%s" % vmci) \
562           or not gdb.parse_and_eval("(%s)->has_vmcoreinfo" % vmci):
563            return
564
565        fmt = gdb.parse_and_eval("(%s)->vmcoreinfo.guest_format" % vmci)
566        addr = gdb.parse_and_eval("(%s)->vmcoreinfo.paddr" % vmci)
567        size = gdb.parse_and_eval("(%s)->vmcoreinfo.size" % vmci)
568
569        fmt = le16_to_cpu(fmt)
570        addr = le64_to_cpu(addr)
571        size = le32_to_cpu(size)
572
573        if fmt != VMCOREINFO_FORMAT_ELF:
574            return
575
576        vmcoreinfo = self.phys_memory_read(addr, size)
577        if vmcoreinfo:
578            self.elf.add_vmcoreinfo_note(bytes(vmcoreinfo))
579
580    def invoke(self, args, from_tty):
581        """Handles command invocation from gdb."""
582
583        # Unwittingly pressing the Enter key after the command should
584        # not dump the same multi-gig coredump to the same file.
585        self.dont_repeat()
586
587        argv = gdb.string_to_argv(args)
588        if len(argv) != 2:
589            raise gdb.GdbError("usage: dump-guest-memory FILE ARCH")
590
591        self.elf = ELF(argv[1])
592        self.guest_phys_blocks = get_guest_phys_blocks()
593        self.add_vmcoreinfo()
594
595        with open(argv[0], "wb") as vmcore:
596            self.dump_init(vmcore)
597            self.dump_iterate(vmcore)
598
599DumpGuestMemory()
600