blob: f2457b1dde2479d3e48e46c6961a52b06f77f6d8 [file] [log] [blame]
Marc-André Lureau83de0ea2019-11-27 14:10:38 +04001#!/usr/bin/env python3
Alexander Grafb1742572015-01-22 15:01:40 +01002#
3# Migration Stream Analyzer
4#
5# Copyright (c) 2015 Alexander Graf <agraf@suse.de>
6#
7# This library is free software; you can redistribute it and/or
8# modify it under the terms of the GNU Lesser General Public
9# License as published by the Free Software Foundation; either
Chetan Pant61f3c912020-10-23 12:44:24 +000010# version 2.1 of the License, or (at your option) any later version.
Alexander Grafb1742572015-01-22 15:01:40 +010011#
12# This library is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15# Lesser General Public License for more details.
16#
17# You should have received a copy of the GNU Lesser General Public
18# License along with this library; if not, see <http://www.gnu.org/licenses/>.
19
Alexander Grafb1742572015-01-22 15:01:40 +010020import json
21import os
22import argparse
23import collections
Marc-André Lureau83de0ea2019-11-27 14:10:38 +040024import struct
25import sys
26
27
Alexander Grafb1742572015-01-22 15:01:40 +010028def mkdir_p(path):
29 try:
30 os.makedirs(path)
31 except OSError:
32 pass
33
Marc-André Lureau83de0ea2019-11-27 14:10:38 +040034
Alexander Grafb1742572015-01-22 15:01:40 +010035class MigrationFile(object):
36 def __init__(self, filename):
37 self.filename = filename
38 self.file = open(self.filename, "rb")
39
40 def read64(self):
Fabiano Rosascaea0322023-10-09 15:43:25 -030041 return int.from_bytes(self.file.read(8), byteorder='big', signed=False)
Alexander Grafb1742572015-01-22 15:01:40 +010042
43 def read32(self):
Fabiano Rosascaea0322023-10-09 15:43:25 -030044 return int.from_bytes(self.file.read(4), byteorder='big', signed=False)
Alexander Grafb1742572015-01-22 15:01:40 +010045
46 def read16(self):
Fabiano Rosascaea0322023-10-09 15:43:25 -030047 return int.from_bytes(self.file.read(2), byteorder='big', signed=False)
Alexander Grafb1742572015-01-22 15:01:40 +010048
49 def read8(self):
Marc-André Lureau83de0ea2019-11-27 14:10:38 +040050 return int.from_bytes(self.file.read(1), byteorder='big', signed=True)
Alexander Grafb1742572015-01-22 15:01:40 +010051
52 def readstr(self, len = None):
Marc-André Lureau83de0ea2019-11-27 14:10:38 +040053 return self.readvar(len).decode('utf-8')
Alexander Grafb1742572015-01-22 15:01:40 +010054
55 def readvar(self, size = None):
56 if size is None:
57 size = self.read8()
58 if size == 0:
59 return ""
60 value = self.file.read(size)
61 if len(value) != size:
62 raise Exception("Unexpected end of %s at 0x%x" % (self.filename, self.file.tell()))
63 return value
64
65 def tell(self):
66 return self.file.tell()
67
68 # The VMSD description is at the end of the file, after EOF. Look for
69 # the last NULL byte, then for the beginning brace of JSON.
70 def read_migration_debug_json(self):
71 QEMU_VM_VMDESCRIPTION = 0x06
72
73 # Remember the offset in the file when we started
74 entrypos = self.file.tell()
75
76 # Read the last 10MB
77 self.file.seek(0, os.SEEK_END)
78 endpos = self.file.tell()
79 self.file.seek(max(-endpos, -10 * 1024 * 1024), os.SEEK_END)
80 datapos = self.file.tell()
81 data = self.file.read()
82 # The full file read closed the file as well, reopen it
83 self.file = open(self.filename, "rb")
84
85 # Find the last NULL byte, then the first brace after that. This should
86 # be the beginning of our JSON data.
Marc-André Lureau13ae8cd2019-11-27 14:10:37 +040087 nulpos = data.rfind(b'\0')
88 jsonpos = data.find(b'{', nulpos)
Alexander Grafb1742572015-01-22 15:01:40 +010089
90 # Check backwards from there and see whether we guessed right
91 self.file.seek(datapos + jsonpos - 5, 0)
92 if self.read8() != QEMU_VM_VMDESCRIPTION:
93 raise Exception("No Debug Migration device found")
94
95 jsonlen = self.read32()
96
97 # Seek back to where we were at the beginning
98 self.file.seek(entrypos, 0)
99
Alexey Kirillov14f9cec2020-07-15 18:21:35 +0300100 # explicit decode() needed for Python 3.5 compatibility
101 return data[jsonpos:jsonpos + jsonlen].decode("utf-8")
Alexander Grafb1742572015-01-22 15:01:40 +0100102
103 def close(self):
104 self.file.close()
105
106class RamSection(object):
107 RAM_SAVE_FLAG_COMPRESS = 0x02
108 RAM_SAVE_FLAG_MEM_SIZE = 0x04
109 RAM_SAVE_FLAG_PAGE = 0x08
110 RAM_SAVE_FLAG_EOS = 0x10
111 RAM_SAVE_FLAG_CONTINUE = 0x20
112 RAM_SAVE_FLAG_XBZRLE = 0x40
113 RAM_SAVE_FLAG_HOOK = 0x80
Marc-André Lureauf1de3092023-09-20 11:54:54 +0400114 RAM_SAVE_FLAG_COMPRESS_PAGE = 0x100
115 RAM_SAVE_FLAG_MULTIFD_FLUSH = 0x200
Alexander Grafb1742572015-01-22 15:01:40 +0100116
117 def __init__(self, file, version_id, ramargs, section_key):
118 if version_id != 4:
119 raise Exception("Unknown RAM version %d" % version_id)
120
121 self.file = file
122 self.section_key = section_key
123 self.TARGET_PAGE_SIZE = ramargs['page_size']
124 self.dump_memory = ramargs['dump_memory']
125 self.write_memory = ramargs['write_memory']
Fabiano Rosasff40c7f2023-10-09 15:43:24 -0300126 self.ignore_shared = ramargs['ignore_shared']
Alexander Grafb1742572015-01-22 15:01:40 +0100127 self.sizeinfo = collections.OrderedDict()
128 self.data = collections.OrderedDict()
129 self.data['section sizes'] = self.sizeinfo
130 self.name = ''
131 if self.write_memory:
132 self.files = { }
133 if self.dump_memory:
134 self.memory = collections.OrderedDict()
135 self.data['memory'] = self.memory
136
137 def __repr__(self):
138 return self.data.__repr__()
139
140 def __str__(self):
141 return self.data.__str__()
142
143 def getDict(self):
144 return self.data
145
146 def read(self):
147 # Read all RAM sections
148 while True:
149 addr = self.file.read64()
150 flags = addr & (self.TARGET_PAGE_SIZE - 1)
151 addr &= ~(self.TARGET_PAGE_SIZE - 1)
152
153 if flags & self.RAM_SAVE_FLAG_MEM_SIZE:
Peter Xu434b8ad2024-01-17 15:58:48 +0800154 total_length = addr
155 while total_length > 0:
Alexander Grafb1742572015-01-22 15:01:40 +0100156 namelen = self.file.read8()
Alexander Grafb1742572015-01-22 15:01:40 +0100157 self.name = self.file.readstr(len = namelen)
158 len = self.file.read64()
Peter Xu434b8ad2024-01-17 15:58:48 +0800159 total_length -= len
Alexander Grafb1742572015-01-22 15:01:40 +0100160 self.sizeinfo[self.name] = '0x%016x' % len
161 if self.write_memory:
Eduardo Habkostf03868b2018-06-08 09:29:43 -0300162 print(self.name)
Alexander Grafb1742572015-01-22 15:01:40 +0100163 mkdir_p('./' + os.path.dirname(self.name))
164 f = open('./' + self.name, "wb")
165 f.truncate(0)
166 f.truncate(len)
167 self.files[self.name] = f
Fabiano Rosasff40c7f2023-10-09 15:43:24 -0300168 if self.ignore_shared:
169 mr_addr = self.file.read64()
Alexander Grafb1742572015-01-22 15:01:40 +0100170 flags &= ~self.RAM_SAVE_FLAG_MEM_SIZE
171
172 if flags & self.RAM_SAVE_FLAG_COMPRESS:
173 if flags & self.RAM_SAVE_FLAG_CONTINUE:
174 flags &= ~self.RAM_SAVE_FLAG_CONTINUE
175 else:
176 self.name = self.file.readstr()
177 fill_char = self.file.read8()
178 # The page in question is filled with fill_char now
179 if self.write_memory and fill_char != 0:
180 self.files[self.name].seek(addr, os.SEEK_SET)
181 self.files[self.name].write(chr(fill_char) * self.TARGET_PAGE_SIZE)
182 if self.dump_memory:
183 self.memory['%s (0x%016x)' % (self.name, addr)] = 'Filled with 0x%02x' % fill_char
184 flags &= ~self.RAM_SAVE_FLAG_COMPRESS
185 elif flags & self.RAM_SAVE_FLAG_PAGE:
186 if flags & self.RAM_SAVE_FLAG_CONTINUE:
187 flags &= ~self.RAM_SAVE_FLAG_CONTINUE
188 else:
189 self.name = self.file.readstr()
190
191 if self.write_memory or self.dump_memory:
192 data = self.file.readvar(size = self.TARGET_PAGE_SIZE)
193 else: # Just skip RAM data
194 self.file.file.seek(self.TARGET_PAGE_SIZE, 1)
195
196 if self.write_memory:
197 self.files[self.name].seek(addr, os.SEEK_SET)
198 self.files[self.name].write(data)
199 if self.dump_memory:
200 hexdata = " ".join("{0:02x}".format(ord(c)) for c in data)
201 self.memory['%s (0x%016x)' % (self.name, addr)] = hexdata
202
203 flags &= ~self.RAM_SAVE_FLAG_PAGE
204 elif flags & self.RAM_SAVE_FLAG_XBZRLE:
205 raise Exception("XBZRLE RAM compression is not supported yet")
206 elif flags & self.RAM_SAVE_FLAG_HOOK:
207 raise Exception("RAM hooks don't make sense with files")
Marc-André Lureauf1de3092023-09-20 11:54:54 +0400208 if flags & self.RAM_SAVE_FLAG_MULTIFD_FLUSH:
209 continue
Alexander Grafb1742572015-01-22 15:01:40 +0100210
211 # End of RAM section
212 if flags & self.RAM_SAVE_FLAG_EOS:
213 break
214
215 if flags != 0:
216 raise Exception("Unknown RAM flags: %x" % flags)
217
218 def __del__(self):
219 if self.write_memory:
220 for key in self.files:
221 self.files[key].close()
222
223
224class HTABSection(object):
225 HASH_PTE_SIZE_64 = 16
226
227 def __init__(self, file, version_id, device, section_key):
228 if version_id != 1:
229 raise Exception("Unknown HTAB version %d" % version_id)
230
231 self.file = file
232 self.section_key = section_key
233
234 def read(self):
235
236 header = self.file.read32()
237
Laurent Vivier029ff892017-12-05 13:27:40 +0100238 if (header == -1):
239 # "no HPT" encoding
240 return
241
Alexander Grafb1742572015-01-22 15:01:40 +0100242 if (header > 0):
243 # First section, just the hash shift
244 return
245
246 # Read until end marker
247 while True:
248 index = self.file.read32()
249 n_valid = self.file.read16()
250 n_invalid = self.file.read16()
251
252 if index == 0 and n_valid == 0 and n_invalid == 0:
253 break
254
Greg Kurzbe7433e2015-02-07 11:25:14 +0100255 self.file.readvar(n_valid * self.HASH_PTE_SIZE_64)
Alexander Grafb1742572015-01-22 15:01:40 +0100256
257 def getDict(self):
258 return ""
259
Mark Cave-Ayland96e5c9b2015-09-05 20:51:48 +0100260
Thomas Huth81c2c9d2023-11-20 12:39:51 +0100261class S390StorageAttributes(object):
262 STATTR_FLAG_EOS = 0x01
263 STATTR_FLAG_MORE = 0x02
264 STATTR_FLAG_ERROR = 0x04
265 STATTR_FLAG_DONE = 0x08
266
267 def __init__(self, file, version_id, device, section_key):
268 if version_id != 0:
269 raise Exception("Unknown storage_attributes version %d" % version_id)
270
271 self.file = file
272 self.section_key = section_key
273
274 def read(self):
275 while True:
276 addr_flags = self.file.read64()
277 flags = addr_flags & 0xfff
278 if (flags & (self.STATTR_FLAG_DONE | self.STATTR_FLAG_EOS)):
279 return
280 if (flags & self.STATTR_FLAG_ERROR):
281 raise Exception("Error in migration stream")
282 count = self.file.read64()
283 self.file.readvar(count)
284
285 def getDict(self):
286 return ""
287
288
Mark Cave-Ayland96e5c9b2015-09-05 20:51:48 +0100289class ConfigurationSection(object):
Fabiano Rosasc36c31c2023-10-09 15:43:22 -0300290 def __init__(self, file, desc):
Mark Cave-Ayland96e5c9b2015-09-05 20:51:48 +0100291 self.file = file
Fabiano Rosasc36c31c2023-10-09 15:43:22 -0300292 self.desc = desc
Fabiano Rosas31499a92023-10-09 15:43:23 -0300293 self.caps = []
294
295 def parse_capabilities(self, vmsd_caps):
296 if not vmsd_caps:
297 return
298
299 ncaps = vmsd_caps.data['caps_count'].data
300 self.caps = vmsd_caps.data['capabilities']
301
302 if type(self.caps) != list:
303 self.caps = [self.caps]
304
305 if len(self.caps) != ncaps:
306 raise Exception("Number of capabilities doesn't match "
307 "caps_count field")
308
309 def has_capability(self, cap):
310 return any([str(c) == cap for c in self.caps])
Mark Cave-Ayland96e5c9b2015-09-05 20:51:48 +0100311
312 def read(self):
Fabiano Rosasc36c31c2023-10-09 15:43:22 -0300313 if self.desc:
314 version_id = self.desc['version']
315 section = VMSDSection(self.file, version_id, self.desc,
316 'configuration')
317 section.read()
Fabiano Rosas31499a92023-10-09 15:43:23 -0300318 self.parse_capabilities(
319 section.data.get("configuration/capabilities"))
Fabiano Rosasc36c31c2023-10-09 15:43:22 -0300320 else:
321 # backward compatibility for older streams that don't have
322 # the configuration section in the json
323 name_len = self.file.read32()
324 name = self.file.readstr(len = name_len)
Mark Cave-Ayland96e5c9b2015-09-05 20:51:48 +0100325
Alexander Grafb1742572015-01-22 15:01:40 +0100326class VMSDFieldGeneric(object):
327 def __init__(self, desc, file):
328 self.file = file
329 self.desc = desc
330 self.data = ""
331
332 def __repr__(self):
333 return str(self.__str__())
334
335 def __str__(self):
Marc-André Lureau83de0ea2019-11-27 14:10:38 +0400336 return " ".join("{0:02x}".format(c) for c in self.data)
Alexander Grafb1742572015-01-22 15:01:40 +0100337
338 def getDict(self):
339 return self.__str__()
340
341 def read(self):
342 size = int(self.desc['size'])
343 self.data = self.file.readvar(size)
344 return self.data
345
Fabiano Rosas31499a92023-10-09 15:43:23 -0300346class VMSDFieldCap(object):
347 def __init__(self, desc, file):
348 self.file = file
349 self.desc = desc
350 self.data = ""
351
352 def __repr__(self):
353 return self.data
354
355 def __str__(self):
356 return self.data
357
358 def read(self):
359 len = self.file.read8()
360 self.data = self.file.readstr(len)
361
362
Alexander Grafb1742572015-01-22 15:01:40 +0100363class VMSDFieldInt(VMSDFieldGeneric):
364 def __init__(self, desc, file):
365 super(VMSDFieldInt, self).__init__(desc, file)
366 self.size = int(desc['size'])
367 self.format = '0x%%0%dx' % (self.size * 2)
368 self.sdtype = '>i%d' % self.size
369 self.udtype = '>u%d' % self.size
370
371 def __repr__(self):
372 if self.data < 0:
373 return ('%s (%d)' % ((self.format % self.udata), self.data))
374 else:
375 return self.format % self.data
376
377 def __str__(self):
378 return self.__repr__()
379
380 def getDict(self):
381 return self.__str__()
382
383 def read(self):
384 super(VMSDFieldInt, self).read()
Marc-André Lureau83de0ea2019-11-27 14:10:38 +0400385 self.sdata = int.from_bytes(self.data, byteorder='big', signed=True)
386 self.udata = int.from_bytes(self.data, byteorder='big', signed=False)
Alexander Grafb1742572015-01-22 15:01:40 +0100387 self.data = self.sdata
388 return self.data
389
390class VMSDFieldUInt(VMSDFieldInt):
391 def __init__(self, desc, file):
392 super(VMSDFieldUInt, self).__init__(desc, file)
393
394 def read(self):
395 super(VMSDFieldUInt, self).read()
396 self.data = self.udata
397 return self.data
398
399class VMSDFieldIntLE(VMSDFieldInt):
400 def __init__(self, desc, file):
401 super(VMSDFieldIntLE, self).__init__(desc, file)
402 self.dtype = '<i%d' % self.size
403
404class VMSDFieldBool(VMSDFieldGeneric):
405 def __init__(self, desc, file):
406 super(VMSDFieldBool, self).__init__(desc, file)
407
408 def __repr__(self):
409 return self.data.__repr__()
410
411 def __str__(self):
412 return self.data.__str__()
413
414 def getDict(self):
415 return self.data
416
417 def read(self):
418 super(VMSDFieldBool, self).read()
419 if self.data[0] == 0:
420 self.data = False
421 else:
422 self.data = True
423 return self.data
424
425class VMSDFieldStruct(VMSDFieldGeneric):
426 QEMU_VM_SUBSECTION = 0x05
427
428 def __init__(self, desc, file):
429 super(VMSDFieldStruct, self).__init__(desc, file)
430 self.data = collections.OrderedDict()
431
Fabiano Rosas86bee9e2025-01-09 15:52:43 -0300432 if 'fields' not in self.desc['struct']:
433 raise Exception("No fields in struct. VMSD:\n%s" % self.desc)
434
Alexander Grafb1742572015-01-22 15:01:40 +0100435 # When we see compressed array elements, unfold them here
436 new_fields = []
437 for field in self.desc['struct']['fields']:
438 if not 'array_len' in field:
439 new_fields.append(field)
440 continue
441 array_len = field.pop('array_len')
442 field['index'] = 0
443 new_fields.append(field)
Marc-André Lureau83de0ea2019-11-27 14:10:38 +0400444 for i in range(1, array_len):
Alexander Grafb1742572015-01-22 15:01:40 +0100445 c = field.copy()
446 c['index'] = i
447 new_fields.append(c)
448
449 self.desc['struct']['fields'] = new_fields
450
451 def __repr__(self):
452 return self.data.__repr__()
453
454 def __str__(self):
455 return self.data.__str__()
456
457 def read(self):
458 for field in self.desc['struct']['fields']:
459 try:
460 reader = vmsd_field_readers[field['type']]
461 except:
462 reader = VMSDFieldGeneric
463
464 field['data'] = reader(field, self.file)
465 field['data'].read()
466
467 if 'index' in field:
468 if field['name'] not in self.data:
469 self.data[field['name']] = []
470 a = self.data[field['name']]
471 if len(a) != int(field['index']):
472 raise Exception("internal index of data field unmatched (%d/%d)" % (len(a), int(field['index'])))
473 a.append(field['data'])
474 else:
475 self.data[field['name']] = field['data']
476
477 if 'subsections' in self.desc['struct']:
478 for subsection in self.desc['struct']['subsections']:
479 if self.file.read8() != self.QEMU_VM_SUBSECTION:
480 raise Exception("Subsection %s not found at offset %x" % ( subsection['vmsd_name'], self.file.tell()))
481 name = self.file.readstr()
482 version_id = self.file.read32()
Fabiano Rosas86bee9e2025-01-09 15:52:43 -0300483
484 if not subsection:
485 raise Exception("Empty description for subsection: %s" % name)
486
Alexander Grafb1742572015-01-22 15:01:40 +0100487 self.data[name] = VMSDSection(self.file, version_id, subsection, (name, 0))
488 self.data[name].read()
489
490 def getDictItem(self, value):
491 # Strings would fall into the array category, treat
492 # them specially
493 if value.__class__ is ''.__class__:
494 return value
495
496 try:
497 return self.getDictOrderedDict(value)
498 except:
499 try:
500 return self.getDictArray(value)
501 except:
502 try:
503 return value.getDict()
504 except:
505 return value
506
507 def getDictArray(self, array):
508 r = []
509 for value in array:
510 r.append(self.getDictItem(value))
511 return r
512
513 def getDictOrderedDict(self, dict):
514 r = collections.OrderedDict()
515 for (key, value) in dict.items():
516 r[key] = self.getDictItem(value)
517 return r
518
519 def getDict(self):
520 return self.getDictOrderedDict(self.data)
521
522vmsd_field_readers = {
523 "bool" : VMSDFieldBool,
524 "int8" : VMSDFieldInt,
525 "int16" : VMSDFieldInt,
526 "int32" : VMSDFieldInt,
527 "int32 equal" : VMSDFieldInt,
528 "int32 le" : VMSDFieldIntLE,
529 "int64" : VMSDFieldInt,
530 "uint8" : VMSDFieldUInt,
531 "uint16" : VMSDFieldUInt,
532 "uint32" : VMSDFieldUInt,
533 "uint32 equal" : VMSDFieldUInt,
534 "uint64" : VMSDFieldUInt,
535 "int64 equal" : VMSDFieldInt,
536 "uint8 equal" : VMSDFieldInt,
537 "uint16 equal" : VMSDFieldInt,
538 "float64" : VMSDFieldGeneric,
539 "timer" : VMSDFieldGeneric,
540 "buffer" : VMSDFieldGeneric,
541 "unused_buffer" : VMSDFieldGeneric,
542 "bitmap" : VMSDFieldGeneric,
543 "struct" : VMSDFieldStruct,
Fabiano Rosas31499a92023-10-09 15:43:23 -0300544 "capability": VMSDFieldCap,
Alexander Grafb1742572015-01-22 15:01:40 +0100545 "unknown" : VMSDFieldGeneric,
546}
547
548class VMSDSection(VMSDFieldStruct):
549 def __init__(self, file, version_id, device, section_key):
550 self.file = file
551 self.data = ""
552 self.vmsd_name = ""
553 self.section_key = section_key
554 desc = device
555 if 'vmsd_name' in device:
556 self.vmsd_name = device['vmsd_name']
557
558 # A section really is nothing but a FieldStruct :)
559 super(VMSDSection, self).__init__({ 'struct' : desc }, file)
560
561###############################################################################
562
563class MigrationDump(object):
564 QEMU_VM_FILE_MAGIC = 0x5145564d
565 QEMU_VM_FILE_VERSION = 0x00000003
566 QEMU_VM_EOF = 0x00
567 QEMU_VM_SECTION_START = 0x01
568 QEMU_VM_SECTION_PART = 0x02
569 QEMU_VM_SECTION_END = 0x03
570 QEMU_VM_SECTION_FULL = 0x04
571 QEMU_VM_SUBSECTION = 0x05
572 QEMU_VM_VMDESCRIPTION = 0x06
Mark Cave-Ayland96e5c9b2015-09-05 20:51:48 +0100573 QEMU_VM_CONFIGURATION = 0x07
Dr. David Alan Gilbert73d9a792015-05-19 12:29:53 +0100574 QEMU_VM_SECTION_FOOTER= 0x7e
Alexander Grafb1742572015-01-22 15:01:40 +0100575
576 def __init__(self, filename):
Thomas Huth81c2c9d2023-11-20 12:39:51 +0100577 self.section_classes = {
578 ( 'ram', 0 ) : [ RamSection, None ],
579 ( 's390-storage_attributes', 0 ) : [ S390StorageAttributes, None],
580 ( 'spapr/htab', 0) : ( HTABSection, None )
581 }
Alexander Grafb1742572015-01-22 15:01:40 +0100582 self.filename = filename
583 self.vmsd_desc = None
Fabiano Rosas86bee9e2025-01-09 15:52:43 -0300584 self.vmsd_json = ""
Alexander Grafb1742572015-01-22 15:01:40 +0100585
Fabiano Rosas86bee9e2025-01-09 15:52:43 -0300586 def read(self, desc_only = False, dump_memory = False,
587 write_memory = False):
Alexander Grafb1742572015-01-22 15:01:40 +0100588 # Read in the whole file
589 file = MigrationFile(self.filename)
Fabiano Rosas86bee9e2025-01-09 15:52:43 -0300590 self.vmsd_json = file.read_migration_debug_json()
Alexander Grafb1742572015-01-22 15:01:40 +0100591
592 # File magic
593 data = file.read32()
594 if data != self.QEMU_VM_FILE_MAGIC:
595 raise Exception("Invalid file magic %x" % data)
596
597 # Version (has to be v3)
598 data = file.read32()
599 if data != self.QEMU_VM_FILE_VERSION:
600 raise Exception("Invalid version number %d" % data)
601
602 self.load_vmsd_json(file)
603
604 # Read sections
605 self.sections = collections.OrderedDict()
606
607 if desc_only:
608 return
609
610 ramargs = {}
611 ramargs['page_size'] = self.vmsd_desc['page_size']
612 ramargs['dump_memory'] = dump_memory
613 ramargs['write_memory'] = write_memory
Fabiano Rosasff40c7f2023-10-09 15:43:24 -0300614 ramargs['ignore_shared'] = False
Alexander Grafb1742572015-01-22 15:01:40 +0100615 self.section_classes[('ram',0)][1] = ramargs
616
617 while True:
618 section_type = file.read8()
619 if section_type == self.QEMU_VM_EOF:
620 break
Mark Cave-Ayland96e5c9b2015-09-05 20:51:48 +0100621 elif section_type == self.QEMU_VM_CONFIGURATION:
Fabiano Rosasc36c31c2023-10-09 15:43:22 -0300622 config_desc = self.vmsd_desc.get('configuration')
623 section = ConfigurationSection(file, config_desc)
Mark Cave-Ayland96e5c9b2015-09-05 20:51:48 +0100624 section.read()
Fabiano Rosasff40c7f2023-10-09 15:43:24 -0300625 ramargs['ignore_shared'] = section.has_capability('x-ignore-shared')
Alexander Grafb1742572015-01-22 15:01:40 +0100626 elif section_type == self.QEMU_VM_SECTION_START or section_type == self.QEMU_VM_SECTION_FULL:
627 section_id = file.read32()
628 name = file.readstr()
629 instance_id = file.read32()
630 version_id = file.read32()
631 section_key = (name, instance_id)
632 classdesc = self.section_classes[section_key]
633 section = classdesc[0](file, version_id, classdesc[1], section_key)
634 self.sections[section_id] = section
635 section.read()
636 elif section_type == self.QEMU_VM_SECTION_PART or section_type == self.QEMU_VM_SECTION_END:
637 section_id = file.read32()
638 self.sections[section_id].read()
Dr. David Alan Gilbert73d9a792015-05-19 12:29:53 +0100639 elif section_type == self.QEMU_VM_SECTION_FOOTER:
640 read_section_id = file.read32()
641 if read_section_id != section_id:
642 raise Exception("Mismatched section footer: %x vs %x" % (read_section_id, section_id))
Alexander Grafb1742572015-01-22 15:01:40 +0100643 else:
644 raise Exception("Unknown section type: %d" % section_type)
645 file.close()
646
647 def load_vmsd_json(self, file):
Fabiano Rosas86bee9e2025-01-09 15:52:43 -0300648 self.vmsd_desc = json.loads(self.vmsd_json,
649 object_pairs_hook=collections.OrderedDict)
Alexander Grafb1742572015-01-22 15:01:40 +0100650 for device in self.vmsd_desc['devices']:
Fabiano Rosas86bee9e2025-01-09 15:52:43 -0300651 if 'fields' not in device:
652 raise Exception("vmstate for device %s has no fields" % device['name'])
Alexander Grafb1742572015-01-22 15:01:40 +0100653 key = (device['name'], device['instance_id'])
654 value = ( VMSDSection, device )
655 self.section_classes[key] = value
656
657 def getDict(self):
658 r = collections.OrderedDict()
659 for (key, value) in self.sections.items():
660 key = "%s (%d)" % ( value.section_key[0], key )
661 r[key] = value.getDict()
662 return r
663
664###############################################################################
665
666class JSONEncoder(json.JSONEncoder):
667 def default(self, o):
668 if isinstance(o, VMSDFieldGeneric):
669 return str(o)
670 return json.JSONEncoder.default(self, o)
671
672parser = argparse.ArgumentParser()
673parser.add_argument("-f", "--file", help='migration dump to read from', required=True)
674parser.add_argument("-m", "--memory", help='dump RAM contents as well', action='store_true')
675parser.add_argument("-d", "--dump", help='what to dump ("state" or "desc")', default='state')
676parser.add_argument("-x", "--extract", help='extract contents into individual files', action='store_true')
677args = parser.parse_args()
678
679jsonenc = JSONEncoder(indent=4, separators=(',', ': '))
680
Fabiano Rosas86bee9e2025-01-09 15:52:43 -0300681if not any([args.extract, args.dump == "state", args.dump == "desc"]):
Laurent Vivierf98d3722021-10-15 15:16:44 +0200682 raise Exception("Please specify either -x, -d state or -d desc")
Fabiano Rosas86bee9e2025-01-09 15:52:43 -0300683
684try:
685 dump = MigrationDump(args.file)
686
687 if args.extract:
688 dump.read(desc_only = True)
689
690 print("desc.json")
691 f = open("desc.json", "w")
692 f.truncate()
693 f.write(jsonenc.encode(dump.vmsd_desc))
694 f.close()
695
696 dump.read(write_memory = True)
697 dict = dump.getDict()
698 print("state.json")
699 f = open("state.json", "w")
700 f.truncate()
701 f.write(jsonenc.encode(dict))
702 f.close()
703 elif args.dump == "state":
704 dump.read(dump_memory = args.memory)
705 dict = dump.getDict()
706 print(jsonenc.encode(dict))
707 elif args.dump == "desc":
708 dump.read(desc_only = True)
709 print(jsonenc.encode(dump.vmsd_desc))
710except Exception:
711 raise Exception("Full JSON dump:\n%s", dump.vmsd_json)