| #!/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 argparse |
| import json |
| import sys |
| |
| taint = 0 |
| |
| def bump_taint(): |
| global taint |
| |
| taint = taint + 1 |
| |
| |
| def check_fields_match(s_field, d_field): |
| if s_field == d_field: |
| return True |
| |
| # Some fields changed names between qemu versions. This list |
| # is used to whitelist such changes. |
| changed_names = [ |
| ['d', 'dev', 'pcidev', 'pci_dev', 'parent_obj'], |
| ['card', 'parent_obj'], |
| ['bridge.dev', 'parent_obj'], |
| ['br.dev', 'parent_obj.parent_obj'], |
| ['port.br.dev', 'parent_obj.parent_obj.parent_obj'], |
| ['port.br.dev.exp.aer_log', |
| 'parent_obj.parent_obj.parent_obj.exp.aer_log'], |
| ['br.dev.exp.aer_log', |
| 'parent_obj.parent_obj.exp.aer_log'], |
| ['shpc', 'bridge.dev.shpc'], |
| ['pci0_status', |
| 'acpi_pci_hotplug.acpi_pcihp_pci_status[0x0]'], |
| ['pci_irq_levels', 'pci_irq_levels_vmstate'], |
| ['usb-ptr-queue', 'HIDPointerEventQueue'], |
| ['num_surfaces', 'ssd.num_surfaces'], |
| ['timer', 'timer_expiry'], |
| ] |
| |
| for grp in changed_names: |
| if s_field in grp and d_field in grp: |
| 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 |
| |
| if not "Description" in fields: |
| return False |
| |
| if not "Fields" in fields["Description"]: |
| return False |
| |
| substruct_fields = fields["Description"]["Fields"] |
| |
| if substruct_fields == []: |
| return False |
| |
| return check_fields_match(substruct_fields[0]["field"], item) |
| |
| |
| 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 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 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" |
| bump_taint() |
| break |
| |
| d_iter = d_iter_list.pop() |
| advance_src = False |
| continue |
| else: |
| advance_dest = True |
| |
| if not check_fields_match(s_item["field"], d_item["field"]): |
| # 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. |
| if exists_in_substruct(d_item, s_item["field"]): |
| # 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 = d_item["Description"]["Fields"] |
| 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) |
| if exists_in_substruct(s_item, d_item["field"]): |
| s_iter_list.append(s_iter) |
| substruct_fields = s_item["Description"]["Fields"] |
| 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" |
| bump_taint() |
| break |
| |
| check_version(s_item, d_item, sec, desc) |
| |
| if not "Description" in s_item: |
| # 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_item in src_sub: |
| found = False |
| for d_item in dest_sub: |
| if s_item["name"] != d_item["name"]: |
| continue |
| |
| found = True |
| check_descriptions(s_item, d_item, sec) |
| |
| if not found: |
| print "Section \"" + sec + "\", Description \"" + desc + "\":", |
| print "Subsection \"" + s_item["name"] + "\" not found" |
| bump_taint() |
| |
| |
| def check_description_in_list(s_item, d_item, sec, desc): |
| if not "Description" in s_item: |
| return |
| |
| if not "Description" in d_item: |
| print "Section \"" + sec + "\", Description \"" + desc + "\",", |
| print "Field \"" + s_item["field"] + "\": missing description" |
| bump_taint() |
| 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" |
| bump_taint() |
| return |
| |
| for f in src_desc: |
| if not f in dest_desc: |
| print "Section \"" + sec + "\"", |
| print "Description \"" + src_desc["name"] + "\":", |
| print "Entry \"" + f + "\" missing" |
| bump_taint() |
| 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"] |
| bump_taint() |
| |
| if not "minimum_version_id" in d: |
| 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"] |
| bump_taint() |
| |
| |
| 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"] |
| bump_taint() |
| |
| |
| def check_machine_type(s, d): |
| 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: |
| temp = src_data |
| src_data = dest_data |
| dest_data = temp |
| |
| for sec in src_data: |
| if not sec in dest_data: |
| print "Section \"" + sec + "\" does not exist in dest" |
| bump_taint() |
| continue |
| |
| s = src_data[sec] |
| d = dest_data[sec] |
| |
| if sec == "vmschkmachine": |
| check_machine_type(s, d) |
| continue |
| |
| check_version(s, d, sec) |
| |
| for entry in s: |
| if not entry in d: |
| print "Section \"" + sec + "\": Entry \"" + entry + "\"", |
| print "missing" |
| bump_taint() |
| continue |
| |
| if entry == "Description": |
| check_descriptions(s[entry], d[entry], sec) |
| |
| return taint |
| |
| |
| if __name__ == '__main__': |
| sys.exit(main()) |