blob: 26ee4c7dba8567a2b699c5a469c8608d25bad09d [file] [log] [blame]
#!/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())