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 qlist_foreach(self
, head
, field_str
):
112 var_p
= head
["lh_first"]
114 var
= var_p
.dereference()
116 var_p
= var
[field_str
]["le_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
.qlist_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])