]>
Commit | Line | Data |
---|---|---|
28fbf8f6 JF |
1 | """ |
2 | This python script adds a new gdb command, "dump-guest-memory". It | |
3 | should be loaded with "source dump-guest-memory.py" at the (gdb) | |
4 | prompt. | |
5 | ||
6 | Copyright (C) 2013, Red Hat, Inc. | |
7 | ||
8 | Authors: | |
9 | Laszlo Ersek <[email protected]> | |
10 | Janosch Frank <[email protected]> | |
11 | ||
12 | This work is licensed under the terms of the GNU GPL, version 2 or later. See | |
13 | the COPYING file in the top-level directory. | |
14 | """ | |
f03868bd | 15 | from __future__ import print_function |
3e16d14f | 16 | |
368e3adc | 17 | import ctypes |
d23bfa91 | 18 | import struct |
3e16d14f | 19 | |
4b17bc93 AJ |
20 | try: |
21 | UINTPTR_T = gdb.lookup_type("uintptr_t") | |
22 | except 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.") | |
47890203 | 26 | |
ca81ce72 JF |
27 | TARGET_PAGE_SIZE = 0x1000 |
28 | TARGET_PAGE_MASK = 0xFFFFFFFFFFFFF000 | |
29 | ||
ca81ce72 JF |
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. | |
33 | PN_XNUM = 0xFFFF | |
34 | ||
368e3adc JF |
35 | EV_CURRENT = 1 |
36 | ||
37 | ELFCLASS32 = 1 | |
38 | ELFCLASS64 = 2 | |
39 | ||
40 | ELFDATA2LSB = 1 | |
41 | ELFDATA2MSB = 2 | |
42 | ||
43 | ET_CORE = 4 | |
44 | ||
45 | PT_LOAD = 1 | |
46 | PT_NOTE = 4 | |
47 | ||
48 | EM_386 = 3 | |
49 | EM_PPC = 20 | |
50 | EM_PPC64 = 21 | |
51 | EM_S390 = 22 | |
52 | EM_AARCH = 183 | |
53 | EM_X86_64 = 62 | |
54 | ||
d23bfa91 MAL |
55 | VMCOREINFO_FORMAT_ELF = 1 |
56 | ||
57 | def le16_to_cpu(val): | |
58 | return struct.unpack("<H", struct.pack("=H", val))[0] | |
59 | ||
60 | def le32_to_cpu(val): | |
61 | return struct.unpack("<I", struct.pack("=I", val))[0] | |
62 | ||
63 | def le64_to_cpu(val): | |
64 | return struct.unpack("<Q", struct.pack("=Q", val))[0] | |
65 | ||
368e3adc JF |
66 | class 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 | |
1d817db3 | 74 | self.endianness = None |
368e3adc JF |
75 | self.elfclass = ELFCLASS64 |
76 | ||
77 | if arch == 'aarch64-le': | |
1d817db3 | 78 | self.endianness = ELFDATA2LSB |
368e3adc | 79 | self.elfclass = ELFCLASS64 |
1d817db3 | 80 | self.ehdr = get_arch_ehdr(self.endianness, self.elfclass) |
368e3adc JF |
81 | self.ehdr.e_machine = EM_AARCH |
82 | ||
83 | elif arch == 'aarch64-be': | |
1d817db3 SW |
84 | self.endianness = ELFDATA2MSB |
85 | self.ehdr = get_arch_ehdr(self.endianness, self.elfclass) | |
368e3adc JF |
86 | self.ehdr.e_machine = EM_AARCH |
87 | ||
88 | elif arch == 'X86_64': | |
1d817db3 SW |
89 | self.endianness = ELFDATA2LSB |
90 | self.ehdr = get_arch_ehdr(self.endianness, self.elfclass) | |
368e3adc JF |
91 | self.ehdr.e_machine = EM_X86_64 |
92 | ||
93 | elif arch == '386': | |
1d817db3 | 94 | self.endianness = ELFDATA2LSB |
368e3adc | 95 | self.elfclass = ELFCLASS32 |
1d817db3 | 96 | self.ehdr = get_arch_ehdr(self.endianness, self.elfclass) |
368e3adc JF |
97 | self.ehdr.e_machine = EM_386 |
98 | ||
99 | elif arch == 's390': | |
1d817db3 SW |
100 | self.endianness = ELFDATA2MSB |
101 | self.ehdr = get_arch_ehdr(self.endianness, self.elfclass) | |
368e3adc JF |
102 | self.ehdr.e_machine = EM_S390 |
103 | ||
104 | elif arch == 'ppc64-le': | |
1d817db3 SW |
105 | self.endianness = ELFDATA2LSB |
106 | self.ehdr = get_arch_ehdr(self.endianness, self.elfclass) | |
368e3adc JF |
107 | self.ehdr.e_machine = EM_PPC64 |
108 | ||
109 | elif arch == 'ppc64-be': | |
1d817db3 SW |
110 | self.endianness = ELFDATA2MSB |
111 | self.ehdr = get_arch_ehdr(self.endianness, self.elfclass) | |
368e3adc JF |
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 | ||
1d817db3 | 125 | note = get_arch_note(self.endianness, len(n_name), len(n_desc)) |
368e3adc JF |
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 | ||
d23bfa91 MAL |
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 | ||
368e3adc JF |
160 | def add_segment(self, p_type, p_paddr, p_size): |
161 | """Adds a segment to the elf.""" | |
162 | ||
1d817db3 | 163 | phdr = get_arch_phdr(self.endianness, self.elfclass) |
368e3adc JF |
164 | phdr.p_type = p_type |
165 | phdr.p_paddr = p_paddr | |
e17bebd0 | 166 | phdr.p_vaddr = p_paddr |
368e3adc JF |
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 | ||
1d817db3 SW |
196 | def get_arch_note(endianness, len_name, len_desc): |
197 | """Returns a Note class with the specified endianness.""" | |
368e3adc | 198 | |
1d817db3 | 199 | if endianness == ELFDATA2LSB: |
368e3adc JF |
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 | ||
217 | class 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 | ||
1d817db3 | 231 | def __init__(self, endianness, elfclass): |
368e3adc JF |
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 | |
1d817db3 | 237 | self.ei_data = endianness |
368e3adc JF |
238 | self.ei_version = EV_CURRENT |
239 | ||
240 | ||
1d817db3 SW |
241 | def get_arch_ehdr(endianness, elfclass): |
242 | """Returns a EHDR64 class with the specified endianness.""" | |
368e3adc | 243 | |
1d817db3 | 244 | if endianness == ELFDATA2LSB: |
368e3adc JF |
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__() | |
1d817db3 | 269 | self.e_ident = Ident(endianness, elfclass) |
368e3adc JF |
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) | |
1d817db3 | 274 | self.e_phentsize = ctypes.sizeof(get_arch_phdr(endianness, elfclass)) |
368e3adc JF |
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__() | |
1d817db3 | 298 | self.e_ident = Ident(endianness, elfclass) |
368e3adc JF |
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) | |
1d817db3 | 303 | self.e_phentsize = ctypes.sizeof(get_arch_phdr(endianness, elfclass)) |
368e3adc JF |
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 | ||
1d817db3 SW |
313 | def get_arch_phdr(endianness, elfclass): |
314 | """Returns a 32 or 64 bit PHDR class with the specified endianness.""" | |
368e3adc | 315 | |
1d817db3 | 316 | if endianness == ELFDATA2LSB: |
368e3adc JF |
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 | ||
ca81ce72 | 351 | |
47890203 | 352 | def int128_get64(val): |
6782c0e7 JF |
353 | """Returns low 64bit part of Int128 struct.""" |
354 | ||
9b4b157e MAL |
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] | |
47890203 | 367 | |
6782c0e7 | 368 | |
47890203 | 369 | def qlist_foreach(head, field_str): |
6782c0e7 JF |
370 | """Generator for qlists.""" |
371 | ||
47890203 | 372 | var_p = head["lh_first"] |
6782c0e7 | 373 | while var_p != 0: |
47890203 | 374 | var = var_p.dereference() |
47890203 | 375 | var_p = var[field_str]["le_next"] |
6782c0e7 JF |
376 | yield var |
377 | ||
47890203 | 378 | |
0878d0e1 | 379 | def qemu_map_ram_ptr(block, offset): |
6782c0e7 JF |
380 | """Returns qemu vaddr for given guest physical address.""" |
381 | ||
0878d0e1 | 382 | return block["host"] + offset |
47890203 | 383 | |
6782c0e7 JF |
384 | |
385 | def 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 | ||
0878d0e1 | 390 | return qemu_map_ram_ptr(memory_region["ram_block"], 0) |
6782c0e7 | 391 | |
47890203 JF |
392 | |
393 | def get_guest_phys_blocks(): | |
6782c0e7 JF |
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 | ||
47890203 | 402 | guest_phys_blocks = [] |
6782c0e7 | 403 | |
7cb1089d JF |
404 | print("guest RAM blocks:") |
405 | print("target_start target_end host_addr message " | |
406 | "count") | |
407 | print("---------------- ---------------- ---------------- ------- " | |
408 | "-----") | |
47890203 JF |
409 | |
410 | current_map_p = gdb.parse_and_eval("address_space_memory.current_map") | |
411 | current_map = current_map_p.dereference() | |
7cb1089d JF |
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"])): | |
6782c0e7 JF |
417 | flat_range = (current_map["ranges"] + cur).dereference() |
418 | memory_region = flat_range["mr"].dereference() | |
47890203 JF |
419 | |
420 | # we only care about RAM | |
7f135356 PB |
421 | if (not memory_region["ram"] or |
422 | memory_region["ram_device"] or | |
423 | memory_region["nonvolatile"]): | |
47890203 JF |
424 | continue |
425 | ||
426 | section_size = int128_get64(flat_range["addr"]["size"]) | |
427 | target_start = int128_get64(flat_range["addr"]["start"]) | |
6782c0e7 JF |
428 | target_end = target_start + section_size |
429 | host_addr = (memory_region_get_ram_ptr(memory_region) | |
430 | + flat_range["offset_in_region"]) | |
47890203 JF |
431 | predecessor = None |
432 | ||
433 | # find continuity in guest physical address space | |
6782c0e7 | 434 | if len(guest_phys_blocks) > 0: |
47890203 JF |
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 | |
6782c0e7 | 441 | assert predecessor["target_end"] <= target_start |
47890203 JF |
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 | ||
6782c0e7 | 449 | if predecessor is None: |
47890203 JF |
450 | # isolated mapping, add it to the list |
451 | guest_phys_blocks.append({"target_start": target_start, | |
6782c0e7 JF |
452 | "target_end": target_end, |
453 | "host_addr": host_addr}) | |
47890203 JF |
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 | ||
7cb1089d JF |
461 | print("%016x %016x %016x %-7s %5u" % |
462 | (target_start, target_end, host_addr.cast(UINTPTR_T), | |
463 | message, len(guest_phys_blocks))) | |
47890203 JF |
464 | |
465 | return guest_phys_blocks | |
466 | ||
467 | ||
28fbf8f6 JF |
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. | |
3e16d14f LE |
472 | class DumpGuestMemory(gdb.Command): |
473 | """Extract guest vmcore from qemu process coredump. | |
474 | ||
368e3adc JF |
475 | The two required arguments are FILE and ARCH: |
476 | FILE identifies the target file to write the guest vmcore to. | |
477 | ARCH specifies the architecture for which the core will be generated. | |
3e16d14f LE |
478 | |
479 | This GDB command reimplements the dump-guest-memory QMP command in | |
480 | python, using the representation of guest memory as captured in the qemu | |
481 | coredump. The qemu process that has been dumped must have had the | |
368e3adc | 482 | command line option "-machine dump-guest-core=on" which is the default. |
3e16d14f LE |
483 | |
484 | For simplicity, the "paging", "begin" and "end" parameters of the QMP | |
485 | command are not supported -- no attempt is made to get the guest's | |
486 | internal paging structures (ie. paging=false is hard-wired), and guest | |
487 | memory is always fully dumped. | |
488 | ||
368e3adc JF |
489 | Currently aarch64-be, aarch64-le, X86_64, 386, s390, ppc64-be, |
490 | ppc64-le guests are supported. | |
3e16d14f LE |
491 | |
492 | The CORE/NT_PRSTATUS and QEMU notes (that is, the VCPUs' statuses) are | |
493 | not written to the vmcore. Preparing these would require context that is | |
494 | only present in the KVM host kernel module when the guest is alive. A | |
495 | fake ELF note is written instead, only to keep the ELF parser of "crash" | |
496 | happy. | |
497 | ||
498 | Dependent on how busted the qemu process was at the time of the | |
499 | coredump, this command might produce unpredictable results. If qemu | |
500 | deliberately called abort(), or it was dumped in response to a signal at | |
501 | a halfway fortunate point, then its coredump should be in reasonable | |
502 | shape and this command should mostly work.""" | |
503 | ||
3e16d14f LE |
504 | def __init__(self): |
505 | super(DumpGuestMemory, self).__init__("dump-guest-memory", | |
506 | gdb.COMMAND_DATA, | |
507 | gdb.COMPLETE_FILENAME) | |
368e3adc | 508 | self.elf = None |
47890203 | 509 | self.guest_phys_blocks = None |
3e16d14f | 510 | |
368e3adc JF |
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 | ||
3e16d14f | 525 | for block in self.guest_phys_blocks: |
368e3adc JF |
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) | |
3e16d14f LE |
530 | |
531 | def dump_iterate(self, vmcore): | |
368e3adc JF |
532 | """Writes guest core to file.""" |
533 | ||
3e16d14f LE |
534 | qemu_core = gdb.inferiors()[0] |
535 | for block in self.guest_phys_blocks: | |
6782c0e7 | 536 | cur = block["host_addr"] |
3e16d14f | 537 | left = block["target_end"] - block["target_start"] |
7cb1089d JF |
538 | print("dumping range at %016x for length %016x" % |
539 | (cur.cast(UINTPTR_T), left)) | |
368e3adc | 540 | |
6782c0e7 | 541 | while left > 0: |
ca81ce72 | 542 | chunk_size = min(TARGET_PAGE_SIZE, left) |
3e16d14f LE |
543 | chunk = qemu_core.read_memory(cur, chunk_size) |
544 | vmcore.write(chunk) | |
6782c0e7 | 545 | cur += chunk_size |
3e16d14f LE |
546 | left -= chunk_size |
547 | ||
d23bfa91 MAL |
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): | |
ce6b9e42 MAL |
558 | if gdb.lookup_symbol("vmcoreinfo_realize")[0] is None: |
559 | return | |
c3b1642b | 560 | vmci = 'vmcoreinfo_realize::vmcoreinfo_state' |
d36d0a9d MAL |
561 | if not gdb.parse_and_eval("%s" % vmci) \ |
562 | or not gdb.parse_and_eval("(%s)->has_vmcoreinfo" % vmci): | |
d23bfa91 MAL |
563 | return |
564 | ||
d36d0a9d MAL |
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) | |
d23bfa91 MAL |
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: | |
6f49ec40 | 578 | self.elf.add_vmcoreinfo_note(bytes(vmcoreinfo)) |
d23bfa91 | 579 | |
3e16d14f | 580 | def invoke(self, args, from_tty): |
368e3adc JF |
581 | """Handles command invocation from gdb.""" |
582 | ||
3e16d14f LE |
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) | |
368e3adc JF |
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() | |
d23bfa91 | 593 | self.add_vmcoreinfo() |
3e16d14f | 594 | |
368e3adc JF |
595 | with open(argv[0], "wb") as vmcore: |
596 | self.dump_init(vmcore) | |
597 | self.dump_iterate(vmcore) | |
3e16d14f LE |
598 | |
599 | DumpGuestMemory() |