1
# This python script adds a new gdb command, "dump-guest-memory". It
2
# should be loaded with "source dump-guest-memory.py" at the (gdb)
5
# Copyright (C) 2013, Red Hat, Inc.
8
# Laszlo Ersek <lersek@redhat.com>
10
# This work is licensed under the terms of the GNU GPL, version 2 or later. See
11
# the COPYING file in the top-level directory.
13
# The leading docstring doesn't have idiomatic Python formatting. It is
14
# printed by gdb's "help" command (the first line is printed in the
15
# "help data" summary), and it should match how other help texts look in
20
class DumpGuestMemory(gdb.Command):
21
"""Extract guest vmcore from qemu process coredump.
23
The sole argument is FILE, identifying the target file to write the
26
This GDB command reimplements the dump-guest-memory QMP command in
27
python, using the representation of guest memory as captured in the qemu
28
coredump. The qemu process that has been dumped must have had the
29
command line option "-machine dump-guest-core=on".
31
For simplicity, the "paging", "begin" and "end" parameters of the QMP
32
command are not supported -- no attempt is made to get the guest's
33
internal paging structures (ie. paging=false is hard-wired), and guest
34
memory is always fully dumped.
36
Only x86_64 guests are supported.
38
The CORE/NT_PRSTATUS and QEMU notes (that is, the VCPUs' statuses) are
39
not written to the vmcore. Preparing these would require context that is
40
only present in the KVM host kernel module when the guest is alive. A
41
fake ELF note is written instead, only to keep the ELF parser of "crash"
44
Dependent on how busted the qemu process was at the time of the
45
coredump, this command might produce unpredictable results. If qemu
46
deliberately called abort(), or it was dumped in response to a signal at
47
a halfway fortunate point, then its coredump should be in reasonable
48
shape and this command should mostly work."""
50
TARGET_PAGE_SIZE = 0x1000
51
TARGET_PAGE_MASK = 0xFFFFFFFFFFFFF000
53
# Various ELF constants
54
EM_X86_64 = 62 # AMD x86-64 target machine
55
ELFDATA2LSB = 1 # little endian
63
# Special value for e_phnum. This indicates that the real number of
64
# program headers is too large to fit into e_phnum. Instead the real
65
# value is in the field sh_info of section 0.
68
# Format strings for packing and header size calculation.
69
ELF64_EHDR = ("4s" # e_ident/magic
89
ELF64_PHDR = ("I" # p_type
100
super(DumpGuestMemory, self).__init__("dump-guest-memory",
102
gdb.COMPLETE_FILENAME)
103
self.uintptr_t = gdb.lookup_type("uintptr_t")
104
self.elf64_ehdr_le = struct.Struct("<%s" % self.ELF64_EHDR)
105
self.elf64_phdr_le = struct.Struct("<%s" % self.ELF64_PHDR)
107
def int128_get64(self, val):
108
assert (val["hi"] == 0)
111
def qtailq_foreach(self, head, field_str):
112
var_p = head["tqh_first"]
114
var = var_p.dereference()
116
var_p = var[field_str]["tqe_next"]
118
def qemu_get_ram_block(self, ram_addr):
119
ram_blocks = gdb.parse_and_eval("ram_list.blocks")
120
for block in self.qtailq_foreach(ram_blocks, "next"):
121
if (ram_addr - block["offset"] < block["length"]):
123
raise gdb.GdbError("Bad ram offset %x" % ram_addr)
125
def qemu_get_ram_ptr(self, ram_addr):
126
block = self.qemu_get_ram_block(ram_addr)
127
return block["host"] + (ram_addr - block["offset"])
129
def memory_region_get_ram_ptr(self, mr):
130
if (mr["alias"] != 0):
131
return (self.memory_region_get_ram_ptr(mr["alias"].dereference()) +
133
return self.qemu_get_ram_ptr(mr["ram_addr"] & self.TARGET_PAGE_MASK)
135
def guest_phys_blocks_init(self):
136
self.guest_phys_blocks = []
138
def guest_phys_blocks_append(self):
139
print "guest RAM blocks:"
140
print ("target_start target_end host_addr message "
142
print ("---------------- ---------------- ---------------- ------- "
145
current_map_p = gdb.parse_and_eval("address_space_memory.current_map")
146
current_map = current_map_p.dereference()
147
for cur in range(current_map["nr"]):
148
flat_range = (current_map["ranges"] + cur).dereference()
149
mr = flat_range["mr"].dereference()
151
# we only care about RAM
155
section_size = self.int128_get64(flat_range["addr"]["size"])
156
target_start = self.int128_get64(flat_range["addr"]["start"])
157
target_end = target_start + section_size
158
host_addr = (self.memory_region_get_ram_ptr(mr) +
159
flat_range["offset_in_region"])
162
# find continuity in guest physical address space
163
if (len(self.guest_phys_blocks) > 0):
164
predecessor = self.guest_phys_blocks[-1]
165
predecessor_size = (predecessor["target_end"] -
166
predecessor["target_start"])
168
# the memory API guarantees monotonically increasing
170
assert (predecessor["target_end"] <= target_start)
172
# we want continuity in both guest-physical and
173
# host-virtual memory
174
if (predecessor["target_end"] < target_start or
175
predecessor["host_addr"] + predecessor_size != host_addr):
178
if (predecessor is None):
179
# isolated mapping, add it to the list
180
self.guest_phys_blocks.append({"target_start": target_start,
181
"target_end" : target_end,
182
"host_addr" : host_addr})
185
# expand predecessor until @target_end; predecessor's
186
# start doesn't change
187
predecessor["target_end"] = target_end
190
print ("%016x %016x %016x %-7s %5u" %
191
(target_start, target_end, host_addr.cast(self.uintptr_t),
192
message, len(self.guest_phys_blocks)))
194
def cpu_get_dump_info(self):
195
# We can't synchronize the registers with KVM post-mortem, and
196
# the bits in (first_x86_cpu->env.hflags) seem to be stale; they
197
# may not reflect long mode for example. Hence just assume the
198
# most common values. This also means that instruction pointer
199
# etc. will be bogus in the dump, but at least the RAM contents
201
self.dump_info = {"d_machine": self.EM_X86_64,
202
"d_endian" : self.ELFDATA2LSB,
203
"d_class" : self.ELFCLASS64}
205
def encode_elf64_ehdr_le(self):
206
return self.elf64_ehdr_le.pack(
207
self.ELFMAG, # e_ident/magic
208
self.dump_info["d_class"], # e_ident/class
209
self.dump_info["d_endian"], # e_ident/data
210
self.EV_CURRENT, # e_ident/version
213
self.ET_CORE, # e_type
214
self.dump_info["d_machine"], # e_machine
215
self.EV_CURRENT, # e_version
217
self.elf64_ehdr_le.size, # e_phoff
220
self.elf64_ehdr_le.size, # e_ehsize
221
self.elf64_phdr_le.size, # e_phentsize
222
self.phdr_num, # e_phnum
228
def encode_elf64_note_le(self):
229
return self.elf64_phdr_le.pack(self.PT_NOTE, # p_type
231
(self.memory_offset -
232
len(self.note)), # p_offset
235
len(self.note), # p_filesz
236
len(self.note), # p_memsz
240
def encode_elf64_load_le(self, offset, start_hwaddr, range_size):
241
return self.elf64_phdr_le.pack(self.PT_LOAD, # p_type
245
start_hwaddr, # p_paddr
246
range_size, # p_filesz
247
range_size, # p_memsz
251
def note_init(self, name, desc, type):
252
# name must include a trailing NUL
253
namesz = (len(name) + 1 + 3) / 4 * 4
254
descsz = (len(desc) + 3) / 4 * 4
255
fmt = ("<" # little endian
262
self.note = struct.pack(fmt,
263
len(name) + 1, len(desc), type, name, desc)
266
self.guest_phys_blocks_init()
267
self.guest_phys_blocks_append()
268
self.cpu_get_dump_info()
269
# we have no way to retrieve the VCPU status from KVM
271
self.note_init("NONE", "EMPTY", 0)
273
# Account for PT_NOTE.
276
# We should never reach PN_XNUM for paging=false dumps: there's
277
# just a handful of discontiguous ranges after merging.
278
self.phdr_num += len(self.guest_phys_blocks)
279
assert (self.phdr_num < self.PN_XNUM)
281
# Calculate the ELF file offset where the memory dump commences:
288
# PT_LOAD: len(self.guest_phys_blocks)
291
self.memory_offset = (self.elf64_ehdr_le.size +
292
self.elf64_phdr_le.size * self.phdr_num +
295
def dump_begin(self, vmcore):
296
vmcore.write(self.encode_elf64_ehdr_le())
297
vmcore.write(self.encode_elf64_note_le())
298
running = self.memory_offset
299
for block in self.guest_phys_blocks:
300
range_size = block["target_end"] - block["target_start"]
301
vmcore.write(self.encode_elf64_load_le(running,
302
block["target_start"],
304
running += range_size
305
vmcore.write(self.note)
307
def dump_iterate(self, vmcore):
308
qemu_core = gdb.inferiors()[0]
309
for block in self.guest_phys_blocks:
310
cur = block["host_addr"]
311
left = block["target_end"] - block["target_start"]
312
print ("dumping range at %016x for length %016x" %
313
(cur.cast(self.uintptr_t), left))
315
chunk_size = min(self.TARGET_PAGE_SIZE, left)
316
chunk = qemu_core.read_memory(cur, chunk_size)
321
def create_vmcore(self, filename):
322
vmcore = open(filename, "wb")
323
self.dump_begin(vmcore)
324
self.dump_iterate(vmcore)
327
def invoke(self, args, from_tty):
328
# Unwittingly pressing the Enter key after the command should
329
# not dump the same multi-gig coredump to the same file.
332
argv = gdb.string_to_argv(args)
334
raise gdb.GdbError("usage: dump-guest-memory FILE")
337
self.create_vmcore(argv[0])