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 UINTPTR_T
= gdb
.lookup_type("uintptr_t")
22 TARGET_PAGE_SIZE
= 0x1000
23 TARGET_PAGE_MASK
= 0xFFFFFFFFFFFFF000
25 # Various ELF constants
26 EM_X86_64
= 62 # AMD x86-64 target machine
27 ELFDATA2LSB
= 1 # little endian
35 # Special value for e_phnum. This indicates that the real number of
36 # program headers is too large to fit into e_phnum. Instead the real
37 # value is in the field sh_info of section 0.
40 # Format strings for packing and header size calculation.
41 ELF64_EHDR
= ("4s" # e_ident/magic
61 ELF64_PHDR
= ("I" # p_type
71 def int128_get64(val
):
72 assert (val
["hi"] == 0)
75 def qlist_foreach(head
, field_str
):
76 var_p
= head
["lh_first"]
78 var
= var_p
.dereference()
80 var_p
= var
[field_str
]["le_next"]
82 def qemu_get_ram_block(ram_addr
):
83 ram_blocks
= gdb
.parse_and_eval("ram_list.blocks")
84 for block
in qlist_foreach(ram_blocks
, "next"):
85 if (ram_addr
- block
["offset"] < block
["used_length"]):
87 raise gdb
.GdbError("Bad ram offset %x" % ram_addr
)
89 def qemu_get_ram_ptr(ram_addr
):
90 block
= qemu_get_ram_block(ram_addr
)
91 return block
["host"] + (ram_addr
- block
["offset"])
93 def memory_region_get_ram_ptr(mr
):
94 if (mr
["alias"] != 0):
95 return (memory_region_get_ram_ptr(mr
["alias"].dereference()) +
97 return qemu_get_ram_ptr(mr
["ram_addr"] & TARGET_PAGE_MASK
)
99 def get_guest_phys_blocks():
100 guest_phys_blocks
= []
101 print "guest RAM blocks:"
102 print ("target_start target_end host_addr message "
104 print ("---------------- ---------------- ---------------- ------- "
107 current_map_p
= gdb
.parse_and_eval("address_space_memory.current_map")
108 current_map
= current_map_p
.dereference()
109 for cur
in range(current_map
["nr"]):
110 flat_range
= (current_map
["ranges"] + cur
).dereference()
111 mr
= flat_range
["mr"].dereference()
113 # we only care about RAM
117 section_size
= int128_get64(flat_range
["addr"]["size"])
118 target_start
= int128_get64(flat_range
["addr"]["start"])
119 target_end
= target_start
+ section_size
120 host_addr
= (memory_region_get_ram_ptr(mr
) +
121 flat_range
["offset_in_region"])
124 # find continuity in guest physical address space
125 if (len(guest_phys_blocks
) > 0):
126 predecessor
= guest_phys_blocks
[-1]
127 predecessor_size
= (predecessor
["target_end"] -
128 predecessor
["target_start"])
130 # the memory API guarantees monotonically increasing
132 assert (predecessor
["target_end"] <= target_start
)
134 # we want continuity in both guest-physical and
135 # host-virtual memory
136 if (predecessor
["target_end"] < target_start
or
137 predecessor
["host_addr"] + predecessor_size
!= host_addr
):
140 if (predecessor
is None):
141 # isolated mapping, add it to the list
142 guest_phys_blocks
.append({"target_start": target_start
,
143 "target_end" : target_end
,
144 "host_addr" : host_addr
})
147 # expand predecessor until @target_end; predecessor's
148 # start doesn't change
149 predecessor
["target_end"] = target_end
152 print ("%016x %016x %016x %-7s %5u" %
153 (target_start
, target_end
, host_addr
.cast(UINTPTR_T
),
154 message
, len(guest_phys_blocks
)))
156 return guest_phys_blocks
159 class DumpGuestMemory(gdb
.Command
):
160 """Extract guest vmcore from qemu process coredump.
162 The sole argument is FILE, identifying the target file to write the
165 This GDB command reimplements the dump-guest-memory QMP command in
166 python, using the representation of guest memory as captured in the qemu
167 coredump. The qemu process that has been dumped must have had the
168 command line option "-machine dump-guest-core=on".
170 For simplicity, the "paging", "begin" and "end" parameters of the QMP
171 command are not supported -- no attempt is made to get the guest's
172 internal paging structures (ie. paging=false is hard-wired), and guest
173 memory is always fully dumped.
175 Only x86_64 guests are supported.
177 The CORE/NT_PRSTATUS and QEMU notes (that is, the VCPUs' statuses) are
178 not written to the vmcore. Preparing these would require context that is
179 only present in the KVM host kernel module when the guest is alive. A
180 fake ELF note is written instead, only to keep the ELF parser of "crash"
183 Dependent on how busted the qemu process was at the time of the
184 coredump, this command might produce unpredictable results. If qemu
185 deliberately called abort(), or it was dumped in response to a signal at
186 a halfway fortunate point, then its coredump should be in reasonable
187 shape and this command should mostly work."""
190 super(DumpGuestMemory
, self
).__init
__("dump-guest-memory",
192 gdb
.COMPLETE_FILENAME
)
193 self
.elf64_ehdr_le
= struct
.Struct("<%s" % ELF64_EHDR
)
194 self
.elf64_phdr_le
= struct
.Struct("<%s" % ELF64_PHDR
)
195 self
.guest_phys_blocks
= None
197 def cpu_get_dump_info(self
):
198 # We can't synchronize the registers with KVM post-mortem, and
199 # the bits in (first_x86_cpu->env.hflags) seem to be stale; they
200 # may not reflect long mode for example. Hence just assume the
201 # most common values. This also means that instruction pointer
202 # etc. will be bogus in the dump, but at least the RAM contents
204 self
.dump_info
= {"d_machine": EM_X86_64
,
205 "d_endian" : ELFDATA2LSB
,
206 "d_class" : ELFCLASS64
}
208 def encode_elf64_ehdr_le(self
):
209 return self
.elf64_ehdr_le
.pack(
210 ELFMAG
, # e_ident/magic
211 self
.dump_info
["d_class"], # e_ident/class
212 self
.dump_info
["d_endian"], # e_ident/data
213 EV_CURRENT
, # e_ident/version
217 self
.dump_info
["d_machine"], # e_machine
218 EV_CURRENT
, # e_version
220 self
.elf64_ehdr_le
.size
, # e_phoff
223 self
.elf64_ehdr_le
.size
, # e_ehsize
224 self
.elf64_phdr_le
.size
, # e_phentsize
225 self
.phdr_num
, # e_phnum
231 def encode_elf64_note_le(self
):
232 return self
.elf64_phdr_le
.pack(PT_NOTE
, # p_type
234 (self
.memory_offset
-
235 len(self
.note
)), # p_offset
238 len(self
.note
), # p_filesz
239 len(self
.note
), # p_memsz
243 def encode_elf64_load_le(self
, offset
, start_hwaddr
, range_size
):
244 return self
.elf64_phdr_le
.pack(PT_LOAD
, # p_type
248 start_hwaddr
, # p_paddr
249 range_size
, # p_filesz
250 range_size
, # p_memsz
254 def note_init(self
, name
, desc
, type):
255 # name must include a trailing NUL
256 namesz
= (len(name
) + 1 + 3) / 4 * 4
257 descsz
= (len(desc
) + 3) / 4 * 4
258 fmt
= ("<" # little endian
265 self
.note
= struct
.pack(fmt
,
266 len(name
) + 1, len(desc
), type, name
, desc
)
269 self
.guest_phys_blocks
= get_guest_phys_blocks()
270 self
.cpu_get_dump_info()
271 # we have no way to retrieve the VCPU status from KVM
273 self
.note_init("NONE", "EMPTY", 0)
275 # Account for PT_NOTE.
278 # We should never reach PN_XNUM for paging=false dumps: there's
279 # just a handful of discontiguous ranges after merging.
280 self
.phdr_num
+= len(self
.guest_phys_blocks
)
281 assert (self
.phdr_num
< PN_XNUM
)
283 # Calculate the ELF file offset where the memory dump commences:
290 # PT_LOAD: len(self.guest_phys_blocks)
293 self
.memory_offset
= (self
.elf64_ehdr_le
.size
+
294 self
.elf64_phdr_le
.size
* self
.phdr_num
+
297 def dump_begin(self
, vmcore
):
298 vmcore
.write(self
.encode_elf64_ehdr_le())
299 vmcore
.write(self
.encode_elf64_note_le())
300 running
= self
.memory_offset
301 for block
in self
.guest_phys_blocks
:
302 range_size
= block
["target_end"] - block
["target_start"]
303 vmcore
.write(self
.encode_elf64_load_le(running
,
304 block
["target_start"],
306 running
+= range_size
307 vmcore
.write(self
.note
)
309 def dump_iterate(self
, vmcore
):
310 qemu_core
= gdb
.inferiors()[0]
311 for block
in self
.guest_phys_blocks
:
312 cur
= block
["host_addr"]
313 left
= block
["target_end"] - block
["target_start"]
314 print ("dumping range at %016x for length %016x" %
315 (cur
.cast(UINTPTR_T
), left
))
317 chunk_size
= min(TARGET_PAGE_SIZE
, left
)
318 chunk
= qemu_core
.read_memory(cur
, chunk_size
)
323 def create_vmcore(self
, filename
):
324 vmcore
= open(filename
, "wb")
325 self
.dump_begin(vmcore
)
326 self
.dump_iterate(vmcore
)
329 def invoke(self
, args
, from_tty
):
330 # Unwittingly pressing the Enter key after the command should
331 # not dump the same multi-gig coredump to the same file.
334 argv
= gdb
.string_to_argv(args
)
336 raise gdb
.GdbError("usage: dump-guest-memory FILE")
339 self
.create_vmcore(argv
[0])