new file mode 100755
@@ -0,0 +1,336 @@
+#!/usr/bin/python
+#
+# Compares vmstate information stored in JSON format, obtained from
+# the -dump-vmstate QEMU command.
+#
+# Copyright 2014 Amit Shah <amit.shah@redhat.com>
+# Copyright 2014 Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, see <http://www.gnu.org/licenses/>.
+
+import json
+import argparse
+
+def check_fields_match(s_field, d_field):
+ # Some fields changed names between qemu versions. This list
+ # is used to whitelist such changes.
+ changed_names = ['dev', 'pcidev', 'pci_dev', 'd', \
+ 'parent_obj', 'parent_obj.parent_obj', \
+ 'parent_obj.parent_obj.parent_obj', \
+ 'bridge.dev', 'bridge.dev.shpc', 'shpc', \
+ 'card', 'port.br.dev', 'port.br.dev.exp.aer_log', \
+ 'br.dev', 'br.dev.exp.aer_log', \
+ 'parent_obj.parent_obj.parent_obj.exp.aer_log', \
+ 'parent_obj.parent_obj.exp.aer_log', \
+ 'pci0_status', \
+ 'acpi_pci_hotplug.acpi_pcihp_pci_status[0x0]', \
+ 'pci_irq_levels', 'pci_irq_levels_vmstate', \
+ 'num_surfaces', 'ssd.num_surfaces', \
+ 'timer', 'timer_expiry', \
+ 'usb-ptr-queue', 'HIDPointerEventQueue' ]
+
+ if s_field == d_field:
+ return True
+
+ if s_field in changed_names and d_field in changed_names:
+ return True
+
+ return False
+
+
+def exists_in_substruct(fields, item):
+ # Some QEMU versions moved a few fields inside a substruct. This
+ # kept the on-wire format the same. This function checks if
+ # something got shifted inside a substruct. For example, the
+ # change in commit 1f42d22233b4f3d1a2933ff30e8d6a6d9ee2d08f
+
+ try:
+ desc = fields["Description"]
+ except KeyError:
+ return False
+
+ try:
+ substruct_fields = desc["Fields"]
+ except KeyError:
+ return False
+
+ d_iter = iter(substruct_fields)
+
+ try:
+ d_item = d_iter.next()
+ except StopIteration:
+ return False
+
+ return check_fields_match(d_item["field"], item)
+
+
+def forward_to_substruct(fields):
+ # We don't need to check for failure here as exists_in_substruct()
+ # would have already done that. This is called only when a
+ # substruct exists.
+
+ desc = fields["Description"]
+ substruct_fields = desc["Fields"]
+
+ return substruct_fields
+
+
+def check_fields(src_fields, dest_fields, desc, sec):
+ # This function checks for all the fields in a section. If some
+ # fields got embedded into a substruct, this function will also
+ # attempt to check inside the substruct.
+
+ d_iter = iter(dest_fields)
+ s_iter = iter(src_fields)
+
+ # Using these lists as stacks to store previous value of s_iter
+ # and d_iter, so that when time comes to exit out of a substruct,
+ # we can go back one level up and continue from where we left off.
+
+ s_iter_list = []
+ d_iter_list = []
+
+ advance_src = True
+ advance_dest = True
+
+ while True:
+ if advance_src:
+ try:
+ s_item = s_iter.next()
+ except StopIteration:
+ if not s_iter_list:
+ break
+
+ s_iter = s_iter_list.pop()
+ continue
+ else:
+ # We want to avoid advancing just once -- when entering a
+ # substruct, or when exiting one.
+ advance_src = True
+
+ if advance_dest:
+ try:
+ d_item = d_iter.next()
+ except StopIteration:
+ if not d_iter_list:
+ # We were not in a substruct
+ print "Section \"" + sec + "\",",
+ print "Description " + "\"" + desc + "\":",
+ print "expected field \"" + s_item["field"] + "\",",
+ print "while dest has no further fields"
+ break
+
+ d_iter = d_iter_list.pop()
+ advance_src = False
+ continue
+ else:
+ advance_dest = True
+
+ match_found = check_fields_match(s_item["field"], d_item["field"])
+
+ if not match_found:
+ in_dest_substruct = False
+ in_src_substruct = False
+ # Some fields were put in substructs, keeping the
+ # on-wire format the same, but breaking static tools
+ # like this one.
+
+ # First, check if dest has a new substruct.
+ in_dest_substruct = exists_in_substruct(d_item, s_item["field"])
+ if in_dest_substruct:
+ # listiterators don't have a prev() function, so we
+ # have to store our current location, descend into the
+ # substruct, and ensure we come out as if nothing
+ # happened when the substruct is over.
+ #
+ # Essentially we're opening the substructs that got
+ # added which didn't change the wire format.
+ d_iter_list.append(d_iter)
+ substruct_fields = forward_to_substruct(d_item)
+ d_iter = iter(substruct_fields)
+ advance_src = False
+ continue
+ else:
+ # Next, check if src has substruct that dest removed
+ # (can happen in backward migration: 2.0 -> 1.5)
+ in_src_substruct = exists_in_substruct(s_item, d_item["field"])
+ if in_src_substruct:
+ s_iter_list.append(s_iter)
+ substruct_fields = forward_to_substruct(s_item)
+ s_iter = iter(substruct_fields)
+ advance_dest = False
+ continue
+ else:
+ print "Section \"" + sec + "\",",
+ print "Description \"" + desc + "\":",
+ print "expected field \"" + s_item["field"] + "\",",
+ print "got \"" + d_item["field"] + "\"; skipping rest"
+ break
+
+ check_version(s_item, d_item, sec, desc)
+
+ try:
+ s_item["Description"]
+ except KeyError:
+ # Check size of this field only if it's not a VMSTRUCT entry
+ check_size(s_item, d_item, sec, desc, s_item["field"])
+
+ check_description_in_list(s_item, d_item, sec, desc)
+
+
+def check_subsections(src_sub, dest_sub, desc, sec):
+ for s_index, s_item in enumerate(src_sub):
+ found = False
+ for d_index, d_item in enumerate(dest_sub):
+ if s_item["name"] != d_item["name"]:
+ continue
+
+ found = True
+ check_descriptions(s_item, d_item, sec)
+
+ if found == False:
+ print "Section \"" + sec + "\", Description \"" + desc + "\":",
+ print "Subsection \"" + s_item["name"] + "\" not found"
+
+
+def check_description_in_list(s_item, d_item, sec, desc):
+ try:
+ s_item["Description"]
+ except KeyError:
+ return
+
+ try:
+ d_item["Description"]
+ except KeyError:
+ print "Section \"" + sec + "\", Description \"" + desc + "\",",
+ print "Field \"" + s_item["field"] + "\": missing description"
+ return
+
+ check_descriptions(s_item["Description"], d_item["Description"], sec)
+
+
+def check_descriptions(src_desc, dest_desc, sec):
+ check_version(src_desc, dest_desc, sec, src_desc["name"])
+
+ if not (check_fields_match(src_desc["name"], dest_desc["name"])):
+ print "Section \"" + sec + "\":",
+ print "Description \"" + src_desc["name"] + "\"",
+ print "missing, got \"" + dest_desc["name"] +"\" instead; skipping"
+ return
+
+ for f in src_desc:
+ try:
+ dest_desc[f]
+ except KeyError:
+ print "Section \"" + sec + "\"",
+ print "Description \"" + src_desc["name"] + "\":",
+ print "Entry \"" + f + "\" missing"
+ continue
+
+ if f == 'Fields':
+ check_fields(src_desc[f], dest_desc[f], src_desc["name"], sec)
+
+ if f == 'Subsections':
+ check_subsections(src_desc[f], dest_desc[f], src_desc["name"], sec)
+
+
+def check_version(s, d, sec, desc=None):
+ if s["version_id"] > d["version_id"]:
+ print "Section \"" + sec + "\"",
+ if desc:
+ print "Description \"" + desc + "\":",
+ print "version error:", s["version_id"], ">", d["version_id"]
+
+ try:
+ d["minimum_version_id"]
+ except KeyError:
+ return
+
+ if s["version_id"] < d["minimum_version_id"]:
+ print "Section \"" + sec + "\"",
+ if desc:
+ print "Description \"" + desc + "\":",
+ print "minimum version error:", s["version_id"], "<",
+ print d["minimum_version_id"]
+
+
+def check_size(s, d, sec, desc=None, field=None):
+ if s["size"] != d["size"]:
+ print "Section \"" + sec + "\"",
+ if desc:
+ print "Description \"" + desc + "\"",
+ if field:
+ print "Field \"" + field + "\"",
+ print "size mismatch:", s["size"], ",", d["size"]
+
+
+def check_machine_type(s, d, sec):
+ if s["Name"] != d["Name"]:
+ print "Warning: checking incompatible machine types:",
+ print "\"" + s["Name"] + "\", \"" + d["Name"] + "\""
+ return
+
+
+def main():
+ parser = argparse.ArgumentParser(description=
+ 'Parse vmstate dumps from QEMU.')
+ parser.add_argument('-s', '--src', type=file, required=True,
+ help='json dump from src qemu')
+ parser.add_argument('-d', '--dest', type=file, required=True,
+ help='json dump from dest qemu')
+ parser.add_argument('--reverse', required=False, default=False,
+ action='store_true',
+ help='reverse the direction')
+ args = parser.parse_args()
+
+ src_data = json.load(args.src)
+ dest_data = json.load(args.dest)
+ args.src.close()
+ args.dest.close()
+
+ if args.reverse == True:
+ temp = src_data
+ src_data = dest_data
+ dest_data = temp
+
+ for sec in src_data:
+ try:
+ dest_data[sec]
+ except KeyError:
+ print "Section \"" + sec + "\" does not exist in dest"
+ continue
+
+ s = src_data[sec]
+ d = dest_data[sec]
+
+ if sec == "vmschkmachine":
+ check_machine_type(s, d, sec);
+ continue;
+
+ check_version(s, d, sec)
+
+ for entry in s:
+ try:
+ d[entry]
+ except KeyError:
+ print "Section \"" + sec + "\": Entry \"" + entry + "\"",
+ print "missing"
+ continue
+
+ if entry == "Description":
+ check_descriptions(s[entry], d[entry], sec)
+
+
+if __name__ == '__main__':
+ main()
This script compares the vmstate dumps in JSON format as output by QEMU with the -dump-vmstate option. It flags various errors, like version mismatch, sections going away, size mismatches, etc. This script is tolerant of a few changes that do not change the on-wire format, like embedding a few fields within substructs. The script takes -s/--src and -d/--dest parameters, to which filenames are given as arguments. Signed-off-by: Amit Shah <amit.shah@redhat.com> --- scripts/vmstate-static-checker.py | 336 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 336 insertions(+) create mode 100755 scripts/vmstate-static-checker.py