3 # Compares vmstate information stored in JSON format, obtained from
4 # the -dump-vmstate QEMU command.
6 # Copyright 2014 Amit Shah <amit.shah@redhat.com>
7 # Copyright 2014 Red Hat, Inc.
9 # This program is free software; you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation; either version 2 of the License, or
12 # (at your option) any later version.
14 # This program is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
19 # You should have received a copy of the GNU General Public License along
20 # with this program; if not, see <http://www.gnu.org/licenses/>.
26 # Count the number of errors found
32 # Ensure we don't wrap around or reset to 0 -- the shell only has
33 # an 8-bit return value.
38 def check_fields_match(name
, s_field
, d_field
):
39 if s_field
== d_field
:
42 # Some fields changed names between qemu versions. This list
43 # is used to whitelist such changes in each section / description.
45 'apic': ['timer', 'timer_expiry'],
46 'e1000': ['dev', 'parent_obj'],
47 'ehci': ['dev', 'pcidev'],
48 'I440FX': ['dev', 'parent_obj'],
49 'ich9_ahci': ['card', 'parent_obj'],
50 'ich9-ahci': ['ahci', 'ich9_ahci'],
51 'ioh3420': ['PCIDevice', 'PCIEDevice'],
52 'ioh-3240-express-root-port': ['port.br.dev',
53 'parent_obj.parent_obj.parent_obj',
54 'port.br.dev.exp.aer_log',
55 'parent_obj.parent_obj.parent_obj.exp.aer_log'],
56 'cirrus_vga': ['hw_cursor_x', 'vga.hw_cursor_x',
57 'hw_cursor_y', 'vga.hw_cursor_y'],
58 'lsiscsi': ['dev', 'parent_obj'],
59 'mch': ['d', 'parent_obj'],
60 'pci_bridge': ['bridge.dev', 'parent_obj', 'bridge.dev.shpc', 'shpc'],
61 'pcnet': ['pci_dev', 'parent_obj'],
62 'PIIX3': ['pci_irq_levels', 'pci_irq_levels_vmstate'],
63 'piix4_pm': ['dev', 'parent_obj', 'pci0_status',
64 'acpi_pci_hotplug.acpi_pcihp_pci_status[0x0]',
65 'pm1a.sts', 'ar.pm1.evt.sts', 'pm1a.en', 'ar.pm1.evt.en',
66 'pm1_cnt.cnt', 'ar.pm1.cnt.cnt',
67 'tmr.timer', 'ar.tmr.timer',
68 'tmr.overflow_time', 'ar.tmr.overflow_time',
70 'rtl8139': ['dev', 'parent_obj'],
71 'qxl': ['num_surfaces', 'ssd.num_surfaces'],
72 'usb-ccid': ['abProtocolDataStructure', 'abProtocolDataStructure.data'],
73 'usb-host': ['dev', 'parent_obj'],
74 'usb-mouse': ['usb-ptr-queue', 'HIDPointerEventQueue'],
75 'usb-tablet': ['usb-ptr-queue', 'HIDPointerEventQueue'],
76 'vmware_vga': ['card', 'parent_obj'],
77 'vmware_vga_internal': ['depth', 'new_depth'],
78 'xhci': ['pci_dev', 'parent_obj'],
79 'x3130-upstream': ['PCIDevice', 'PCIEDevice'],
80 'xio3130-express-downstream-port': ['port.br.dev',
81 'parent_obj.parent_obj.parent_obj',
82 'port.br.dev.exp.aer_log',
83 'parent_obj.parent_obj.parent_obj.exp.aer_log'],
84 'xio3130-downstream': ['PCIDevice', 'PCIEDevice'],
85 'xio3130-express-upstream-port': ['br.dev', 'parent_obj.parent_obj',
87 'parent_obj.parent_obj.exp.aer_log'],
88 'spapr_pci': ['dma_liobn[0]', 'mig_liobn',
89 'mem_win_addr', 'mig_mem_win_addr',
90 'mem_win_size', 'mig_mem_win_size',
91 'io_win_addr', 'mig_io_win_addr',
92 'io_win_size', 'mig_io_win_size'],
95 if not name
in changed_names
:
98 if s_field
in changed_names
[name
] and d_field
in changed_names
[name
]:
103 def get_changed_sec_name(sec
):
104 # Section names can change -- see commit 292b1634 for an example.
106 "ICH9 LPC": "ICH9-LPC",
107 "e1000-82540em": "e1000",
113 if changes
[item
] == sec
:
117 def exists_in_substruct(fields
, item
):
118 # Some QEMU versions moved a few fields inside a substruct. This
119 # kept the on-wire format the same. This function checks if
120 # something got shifted inside a substruct. For example, the
121 # change in commit 1f42d22233b4f3d1a2933ff30e8d6a6d9ee2d08f
123 if not "Description" in fields
:
126 if not "Fields" in fields
["Description"]:
129 substruct_fields
= fields
["Description"]["Fields"]
131 if substruct_fields
== []:
134 return check_fields_match(fields
["Description"]["name"],
135 substruct_fields
[0]["field"], item
)
138 def check_fields(src_fields
, dest_fields
, desc
, sec
):
139 # This function checks for all the fields in a section. If some
140 # fields got embedded into a substruct, this function will also
141 # attempt to check inside the substruct.
143 d_iter
= iter(dest_fields
)
144 s_iter
= iter(src_fields
)
146 # Using these lists as stacks to store previous value of s_iter
147 # and d_iter, so that when time comes to exit out of a substruct,
148 # we can go back one level up and continue from where we left off.
160 s_item
= next(s_iter
)
161 except StopIteration:
162 if s_iter_list
== []:
165 s_iter
= s_iter_list
.pop()
168 if unused_count
== 0:
169 # We want to avoid advancing just once -- when entering a
170 # dest substruct, or when exiting one.
175 d_item
= next(d_iter
)
176 except StopIteration:
177 if d_iter_list
== []:
178 # We were not in a substruct
179 print("Section \"" + sec
+ "\",", end
=' ')
180 print("Description " + "\"" + desc
+ "\":", end
=' ')
181 print("expected field \"" + s_item
["field"] + "\",", end
=' ')
182 print("while dest has no further fields")
186 d_iter
= d_iter_list
.pop()
190 if unused_count
== 0:
193 if unused_count
!= 0:
194 if advance_dest
== False:
195 unused_count
= unused_count
- s_item
["size"]
196 if unused_count
== 0:
200 print("Section \"" + sec
+ "\",", end
=' ')
201 print("Description \"" + desc
+ "\":", end
=' ')
202 print("unused size mismatch near \"", end
=' ')
203 print(s_item
["field"] + "\"")
208 if advance_src
== False:
209 unused_count
= unused_count
- d_item
["size"]
210 if unused_count
== 0:
214 print("Section \"" + sec
+ "\",", end
=' ')
215 print("Description \"" + desc
+ "\":", end
=' ')
216 print("unused size mismatch near \"", end
=' ')
217 print(d_item
["field"] + "\"")
222 if not check_fields_match(desc
, s_item
["field"], d_item
["field"]):
223 # Some fields were put in substructs, keeping the
224 # on-wire format the same, but breaking static tools
227 # First, check if dest has a new substruct.
228 if exists_in_substruct(d_item
, s_item
["field"]):
229 # listiterators don't have a prev() function, so we
230 # have to store our current location, descend into the
231 # substruct, and ensure we come out as if nothing
232 # happened when the substruct is over.
234 # Essentially we're opening the substructs that got
235 # added which didn't change the wire format.
236 d_iter_list
.append(d_iter
)
237 substruct_fields
= d_item
["Description"]["Fields"]
238 d_iter
= iter(substruct_fields
)
242 # Next, check if src has substruct that dest removed
243 # (can happen in backward migration: 2.0 -> 1.5)
244 if exists_in_substruct(s_item
, d_item
["field"]):
245 s_iter_list
.append(s_iter
)
246 substruct_fields
= s_item
["Description"]["Fields"]
247 s_iter
= iter(substruct_fields
)
251 if s_item
["field"] == "unused" or d_item
["field"] == "unused":
252 if s_item
["size"] == d_item
["size"]:
255 if d_item
["field"] == "unused":
257 unused_count
= d_item
["size"] - s_item
["size"]
260 if s_item
["field"] == "unused":
262 unused_count
= s_item
["size"] - d_item
["size"]
265 print("Section \"" + sec
+ "\",", end
=' ')
266 print("Description \"" + desc
+ "\":", end
=' ')
267 print("expected field \"" + s_item
["field"] + "\",", end
=' ')
268 print("got \"" + d_item
["field"] + "\"; skipping rest")
272 check_version(s_item
, d_item
, sec
, desc
)
274 if not "Description" in s_item
:
275 # Check size of this field only if it's not a VMSTRUCT entry
276 check_size(s_item
, d_item
, sec
, desc
, s_item
["field"])
278 check_description_in_list(s_item
, d_item
, sec
, desc
)
281 def check_subsections(src_sub
, dest_sub
, desc
, sec
):
282 for s_item
in src_sub
:
284 for d_item
in dest_sub
:
285 if s_item
["name"] != d_item
["name"]:
289 check_descriptions(s_item
, d_item
, sec
)
292 print("Section \"" + sec
+ "\", Description \"" + desc
+ "\":", end
=' ')
293 print("Subsection \"" + s_item
["name"] + "\" not found")
297 def check_description_in_list(s_item
, d_item
, sec
, desc
):
298 if not "Description" in s_item
:
301 if not "Description" in d_item
:
302 print("Section \"" + sec
+ "\", Description \"" + desc
+ "\",", end
=' ')
303 print("Field \"" + s_item
["field"] + "\": missing description")
307 check_descriptions(s_item
["Description"], d_item
["Description"], sec
)
310 def check_descriptions(src_desc
, dest_desc
, sec
):
311 check_version(src_desc
, dest_desc
, sec
, src_desc
["name"])
313 if not check_fields_match(sec
, src_desc
["name"], dest_desc
["name"]):
314 print("Section \"" + sec
+ "\":", end
=' ')
315 print("Description \"" + src_desc
["name"] + "\"", end
=' ')
316 print("missing, got \"" + dest_desc
["name"] + "\" instead; skipping")
321 if not f
in dest_desc
:
322 print("Section \"" + sec
+ "\"", end
=' ')
323 print("Description \"" + src_desc
["name"] + "\":", end
=' ')
324 print("Entry \"" + f
+ "\" missing")
329 check_fields(src_desc
[f
], dest_desc
[f
], src_desc
["name"], sec
)
331 if f
== 'Subsections':
332 check_subsections(src_desc
[f
], dest_desc
[f
], src_desc
["name"], sec
)
335 def check_version(s
, d
, sec
, desc
=None):
336 if s
["version_id"] > d
["version_id"]:
337 print("Section \"" + sec
+ "\"", end
=' ')
339 print("Description \"" + desc
+ "\":", end
=' ')
340 print("version error:", s
["version_id"], ">", d
["version_id"])
343 if not "minimum_version_id" in d
:
346 if s
["version_id"] < d
["minimum_version_id"]:
347 print("Section \"" + sec
+ "\"", end
=' ')
349 print("Description \"" + desc
+ "\":", end
=' ')
350 print("minimum version error:", s
["version_id"], "<", end
=' ')
351 print(d
["minimum_version_id"])
355 def check_size(s
, d
, sec
, desc
=None, field
=None):
356 if s
["size"] != d
["size"]:
357 print("Section \"" + sec
+ "\"", end
=' ')
359 print("Description \"" + desc
+ "\"", end
=' ')
361 print("Field \"" + field
+ "\"", end
=' ')
362 print("size mismatch:", s
["size"], ",", d
["size"])
366 def check_machine_type(s
, d
):
367 if s
["Name"] != d
["Name"]:
368 print("Warning: checking incompatible machine types:", end
=' ')
369 print("\"" + s
["Name"] + "\", \"" + d
["Name"] + "\"")
374 help_text
= "Parse JSON-formatted vmstate dumps from QEMU in files SRC and DEST. Checks whether migration from SRC to DEST QEMU versions would break based on the VMSTATE information contained within the JSON outputs. The JSON output is created from a QEMU invocation with the -dump-vmstate parameter and a filename argument to it. Other parameters to QEMU do not matter, except the -M (machine type) parameter."
376 parser
= argparse
.ArgumentParser(description
=help_text
)
377 parser
.add_argument('-s', '--src', type=argparse
.FileType('r'),
379 help='json dump from src qemu')
380 parser
.add_argument('-d', '--dest', type=argparse
.FileType('r'),
382 help='json dump from dest qemu')
383 parser
.add_argument('--reverse', required
=False, default
=False,
385 help='reverse the direction')
386 args
= parser
.parse_args()
388 src_data
= json
.load(args
.src
)
389 dest_data
= json
.load(args
.dest
)
400 if not dest_sec
in dest_data
:
401 # Either the section name got changed, or the section
402 # doesn't exist in dest.
403 dest_sec
= get_changed_sec_name(sec
)
404 if not dest_sec
in dest_data
:
405 print("Section \"" + sec
+ "\" does not exist in dest")
410 d
= dest_data
[dest_sec
]
412 if sec
== "vmschkmachine":
413 check_machine_type(s
, d
)
416 check_version(s
, d
, sec
)
420 print("Section \"" + sec
+ "\": Entry \"" + entry
+ "\"", end
=' ')
425 if entry
== "Description":
426 check_descriptions(s
[entry
], d
[entry
], sec
)
431 if __name__
== '__main__':