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) 3# prompt. 4# 5# Copyright (C) 2013, Red Hat, Inc. 6# 7# Authors: 8# Laszlo Ersek <lersek@redhat.com> 9# 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. 12# 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 16# gdb. 17 18import struct 19 20class DumpGuestMemory(gdb.Command): 21 """Extract guest vmcore from qemu process coredump. 22 23The sole argument is FILE, identifying the target file to write the 24guest vmcore to. 25 26This GDB command reimplements the dump-guest-memory QMP command in 27python, using the representation of guest memory as captured in the qemu 28coredump. The qemu process that has been dumped must have had the 29command line option "-machine dump-guest-core=on". 30 31For simplicity, the "paging", "begin" and "end" parameters of the QMP 32command are not supported -- no attempt is made to get the guest's 33internal paging structures (ie. paging=false is hard-wired), and guest 34memory is always fully dumped. 35 36Only x86_64 guests are supported. 37 38The CORE/NT_PRSTATUS and QEMU notes (that is, the VCPUs' statuses) are 39not written to the vmcore. Preparing these would require context that is 40only present in the KVM host kernel module when the guest is alive. A 41fake ELF note is written instead, only to keep the ELF parser of "crash" 42happy. 43 44Dependent on how busted the qemu process was at the time of the 45coredump, this command might produce unpredictable results. If qemu 46deliberately called abort(), or it was dumped in response to a signal at 47a halfway fortunate point, then its coredump should be in reasonable 48shape and this command should mostly work.""" 49 50 TARGET_PAGE_SIZE = 0x1000 51 TARGET_PAGE_MASK = 0xFFFFFFFFFFFFF000 52 53 # Various ELF constants 54 EM_X86_64 = 62 # AMD x86-64 target machine 55 ELFDATA2LSB = 1 # little endian 56 ELFCLASS64 = 2 57 ELFMAG = "\x7FELF" 58 EV_CURRENT = 1 59 ET_CORE = 4 60 PT_LOAD = 1 61 PT_NOTE = 4 62 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. 66 PN_XNUM = 0xFFFF 67 68 # Format strings for packing and header size calculation. 69 ELF64_EHDR = ("4s" # e_ident/magic 70 "B" # e_ident/class 71 "B" # e_ident/data 72 "B" # e_ident/version 73 "B" # e_ident/osabi 74 "8s" # e_ident/pad 75 "H" # e_type 76 "H" # e_machine 77 "I" # e_version 78 "Q" # e_entry 79 "Q" # e_phoff 80 "Q" # e_shoff 81 "I" # e_flags 82 "H" # e_ehsize 83 "H" # e_phentsize 84 "H" # e_phnum 85 "H" # e_shentsize 86 "H" # e_shnum 87 "H" # e_shstrndx 88 ) 89 ELF64_PHDR = ("I" # p_type 90 "I" # p_flags 91 "Q" # p_offset 92 "Q" # p_vaddr 93 "Q" # p_paddr 94 "Q" # p_filesz 95 "Q" # p_memsz 96 "Q" # p_align 97 ) 98 99 def __init__(self): 100 super(DumpGuestMemory, self).__init__("dump-guest-memory", 101 gdb.COMMAND_DATA, 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) 106 107 def int128_get64(self, val): 108 assert (val["hi"] == 0) 109 return val["lo"] 110 111 def qlist_foreach(self, head, field_str): 112 var_p = head["lh_first"] 113 while (var_p != 0): 114 var = var_p.dereference() 115 yield var 116 var_p = var[field_str]["le_next"] 117 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["used_length"]): 122 return block 123 raise gdb.GdbError("Bad ram offset %x" % ram_addr) 124 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"]) 128 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()) + 132 mr["alias_offset"]) 133 return self.qemu_get_ram_ptr(mr["ram_addr"] & self.TARGET_PAGE_MASK) 134 135 def guest_phys_blocks_init(self): 136 self.guest_phys_blocks = [] 137 138 def guest_phys_blocks_append(self): 139 print "guest RAM blocks:" 140 print ("target_start target_end host_addr message " 141 "count") 142 print ("---------------- ---------------- ---------------- ------- " 143 "-----") 144 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() 150 151 # we only care about RAM 152 if (not mr["ram"]): 153 continue 154 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"]) 160 predecessor = None 161 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"]) 167 168 # the memory API guarantees monotonically increasing 169 # traversal 170 assert (predecessor["target_end"] <= target_start) 171 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): 176 predecessor = None 177 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}) 183 message = "added" 184 else: 185 # expand predecessor until @target_end; predecessor's 186 # start doesn't change 187 predecessor["target_end"] = target_end 188 message = "joined" 189 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))) 193 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 200 # should be valid. 201 self.dump_info = {"d_machine": self.EM_X86_64, 202 "d_endian" : self.ELFDATA2LSB, 203 "d_class" : self.ELFCLASS64} 204 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 211 0, # e_ident/osabi 212 "", # e_ident/pad 213 self.ET_CORE, # e_type 214 self.dump_info["d_machine"], # e_machine 215 self.EV_CURRENT, # e_version 216 0, # e_entry 217 self.elf64_ehdr_le.size, # e_phoff 218 0, # e_shoff 219 0, # e_flags 220 self.elf64_ehdr_le.size, # e_ehsize 221 self.elf64_phdr_le.size, # e_phentsize 222 self.phdr_num, # e_phnum 223 0, # e_shentsize 224 0, # e_shnum 225 0 # e_shstrndx 226 ) 227 228 def encode_elf64_note_le(self): 229 return self.elf64_phdr_le.pack(self.PT_NOTE, # p_type 230 0, # p_flags 231 (self.memory_offset - 232 len(self.note)), # p_offset 233 0, # p_vaddr 234 0, # p_paddr 235 len(self.note), # p_filesz 236 len(self.note), # p_memsz 237 0 # p_align 238 ) 239 240 def encode_elf64_load_le(self, offset, start_hwaddr, range_size): 241 return self.elf64_phdr_le.pack(self.PT_LOAD, # p_type 242 0, # p_flags 243 offset, # p_offset 244 0, # p_vaddr 245 start_hwaddr, # p_paddr 246 range_size, # p_filesz 247 range_size, # p_memsz 248 0 # p_align 249 ) 250 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 256 "I" # n_namesz 257 "I" # n_descsz 258 "I" # n_type 259 "%us" # name 260 "%us" # desc 261 % (namesz, descsz)) 262 self.note = struct.pack(fmt, 263 len(name) + 1, len(desc), type, name, desc) 264 265 def dump_init(self): 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 270 # post-mortem 271 self.note_init("NONE", "EMPTY", 0) 272 273 # Account for PT_NOTE. 274 self.phdr_num = 1 275 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) 280 281 # Calculate the ELF file offset where the memory dump commences: 282 # 283 # ELF header 284 # PT_NOTE 285 # PT_LOAD: 1 286 # PT_LOAD: 2 287 # ... 288 # PT_LOAD: len(self.guest_phys_blocks) 289 # ELF note 290 # memory dump 291 self.memory_offset = (self.elf64_ehdr_le.size + 292 self.elf64_phdr_le.size * self.phdr_num + 293 len(self.note)) 294 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"], 303 range_size)) 304 running += range_size 305 vmcore.write(self.note) 306 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)) 314 while (left > 0): 315 chunk_size = min(self.TARGET_PAGE_SIZE, left) 316 chunk = qemu_core.read_memory(cur, chunk_size) 317 vmcore.write(chunk) 318 cur += chunk_size 319 left -= chunk_size 320 321 def create_vmcore(self, filename): 322 vmcore = open(filename, "wb") 323 self.dump_begin(vmcore) 324 self.dump_iterate(vmcore) 325 vmcore.close() 326 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. 330 self.dont_repeat() 331 332 argv = gdb.string_to_argv(args) 333 if (len(argv) != 1): 334 raise gdb.GdbError("usage: dump-guest-memory FILE") 335 336 self.dump_init() 337 self.create_vmcore(argv[0]) 338 339DumpGuestMemory() 340