From: Amit Shah <amit.shah@redhat.com>
To: qemu list <qemu-devel@nongnu.org>
Cc: "Juan Quintela" <quintela@redhat.com>,
"Markus Armbruster" <armbru@redhat.com>,
"Alexander Graf" <agraf@suse.de>,
"Dr. David Alan Gilbert" <dgilbert@redhat.com>,
"Amit Shah" <amit.shah@redhat.com>,
"Paolo Bonzini" <pbonzini@redhat.com>,
"Andreas Färber" <afaerber@suse.de>
Subject: [Qemu-devel] [PATCH v4 02/18] vmstate-static-checker: script to validate vmstate changes
Date: Wed, 18 Jun 2014 13:43:36 +0530 [thread overview]
Message-ID: <32593ed6bfc27fe8192677a69aa2adaa7ed9c11b.1403079139.git.amit.shah@redhat.com> (raw)
In-Reply-To: <cover.1403079139.git.amit.shah@redhat.com>
In-Reply-To: <cover.1403079139.git.amit.shah@redhat.com>
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 | 329 ++++++++++++++++++++++++++++++++++++++
1 file changed, 329 insertions(+)
create mode 100755 scripts/vmstate-static-checker.py
diff --git a/scripts/vmstate-static-checker.py b/scripts/vmstate-static-checker.py
new file mode 100755
index 0000000..cbb4c6f
--- /dev/null
+++ b/scripts/vmstate-static-checker.py
@@ -0,0 +1,329 @@
+#!/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
+
+ # Ensure we don't wrap around or reset to 0 -- the shell only has
+ # an 8-bit return value.
+ if taint < 255:
+ 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())
--
1.9.3
next prev parent reply other threads:[~2014-06-18 8:14 UTC|newest]
Thread overview: 44+ messages / expand[flat|nested] mbox.gz Atom feed top
2014-06-18 8:13 [Qemu-devel] [PATCH v4 00/18] migration: add static analysis tool to check vmstate compat Amit Shah
2014-06-18 8:13 ` [Qemu-devel] [PATCH v4 01/18] migration: dump vmstate info as a json file for static analysis Amit Shah
2014-06-18 10:24 ` Juan Quintela
2014-06-18 10:36 ` Amit Shah
2014-06-18 10:56 ` Juan Quintela
2014-06-18 8:13 ` Amit Shah [this message]
2014-06-18 10:44 ` [Qemu-devel] [PATCH v4 02/18] vmstate-static-checker: script to validate vmstate changes Juan Quintela
2014-06-18 10:58 ` Amit Shah
2014-06-18 11:25 ` Juan Quintela
2014-06-18 11:32 ` Amit Shah
2014-06-18 8:13 ` [Qemu-devel] [PATCH v4 03/18] tests: vmstate static checker: add dump1 and dump2 files Amit Shah
2014-06-18 10:33 ` Juan Quintela
2014-06-18 8:13 ` [Qemu-devel] [PATCH v4 04/18] tests: vmstate static checker: incompat machine types Amit Shah
2014-06-18 10:33 ` Juan Quintela
2014-06-18 8:13 ` [Qemu-devel] [PATCH v4 05/18] tests: vmstate static checker: add version error in main section Amit Shah
2014-06-18 10:33 ` Juan Quintela
2014-06-18 8:13 ` [Qemu-devel] [PATCH v4 06/18] tests: vmstate static checker: version mismatch inside a Description Amit Shah
2014-06-18 10:34 ` Juan Quintela
2014-06-18 8:13 ` [Qemu-devel] [PATCH v4 07/18] tests: vmstate static checker: minimum_version_id check Amit Shah
2014-06-18 10:34 ` Juan Quintela
2014-06-18 8:13 ` [Qemu-devel] [PATCH v4 08/18] tests: vmstate static checker: remove a section Amit Shah
2014-06-18 10:35 ` Juan Quintela
2014-06-18 8:13 ` [Qemu-devel] [PATCH v4 09/18] tests: vmstate static checker: remove a field Amit Shah
2014-06-18 10:35 ` Juan Quintela
2014-06-18 8:13 ` [Qemu-devel] [PATCH v4 10/18] tests: vmstate static checker: remove last field in a struct Amit Shah
2014-06-18 10:36 ` Juan Quintela
2014-06-18 8:13 ` [Qemu-devel] [PATCH v4 11/18] tests: vmstate static checker: change description name Amit Shah
2014-06-18 10:36 ` Juan Quintela
2014-06-18 8:13 ` [Qemu-devel] [PATCH v4 12/18] tests: vmstate static checker: remove Fields Amit Shah
2014-06-18 10:36 ` Juan Quintela
2014-06-18 8:13 ` [Qemu-devel] [PATCH v4 13/18] tests: vmstate static checker: remove Description Amit Shah
2014-06-18 10:37 ` Juan Quintela
2014-06-18 8:13 ` [Qemu-devel] [PATCH v4 14/18] tests: vmstate static checker: remove Description inside Fields Amit Shah
2014-06-18 10:37 ` Juan Quintela
2014-06-18 8:13 ` [Qemu-devel] [PATCH v4 15/18] tests: vmstate static checker: remove a subsection Amit Shah
2014-06-18 10:37 ` Juan Quintela
2014-06-18 8:13 ` [Qemu-devel] [PATCH v4 16/18] tests: vmstate static checker: remove Subsections Amit Shah
2014-06-18 10:38 ` Juan Quintela
2014-06-18 8:13 ` [Qemu-devel] [PATCH v4 17/18] tests: vmstate static checker: add substructure for usb-kbd for hid section Amit Shah
2014-06-18 10:38 ` Juan Quintela
2014-06-18 8:13 ` [Qemu-devel] [PATCH v4 18/18] tests: vmstate static checker: add size mismatch inside substructure Amit Shah
2014-06-18 10:38 ` Juan Quintela
2014-06-18 10:49 ` [Qemu-devel] [PATCH v4 00/18] migration: add static analysis tool to check vmstate compat Juan Quintela
2014-06-18 11:02 ` Amit Shah
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=32593ed6bfc27fe8192677a69aa2adaa7ed9c11b.1403079139.git.amit.shah@redhat.com \
--to=amit.shah@redhat.com \
--cc=afaerber@suse.de \
--cc=agraf@suse.de \
--cc=armbru@redhat.com \
--cc=dgilbert@redhat.com \
--cc=pbonzini@redhat.com \
--cc=qemu-devel@nongnu.org \
--cc=quintela@redhat.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).