vmstate static checker: detect section renames
[qemu/cris-port.git] / scripts / vmstate-static-checker.py
blob3bae769a3781dd4ec2ca967bf3ac75979b75376d
1 #!/usr/bin/python
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/>.
22 import argparse
23 import json
24 import sys
26 # Count the number of errors found
27 taint = 0
29 def bump_taint():
30 global taint
32 # Ensure we don't wrap around or reset to 0 -- the shell only has
33 # an 8-bit return value.
34 if taint < 255:
35 taint = taint + 1
38 def check_fields_match(name, s_field, d_field):
39 if s_field == d_field:
40 return True
42 # Some fields changed names between qemu versions. This list
43 # is used to whitelist such changes in each section / description.
44 changed_names = {
45 'e1000': ['dev', 'parent_obj'],
46 'ehci': ['dev', 'pcidev'],
47 'I440FX': ['dev', 'parent_obj'],
48 'ich9_ahci': ['card', 'parent_obj'],
49 'ioh-3240-express-root-port': ['port.br.dev',
50 'parent_obj.parent_obj.parent_obj',
51 'port.br.dev.exp.aer_log',
52 'parent_obj.parent_obj.parent_obj.exp.aer_log'],
53 'mch': ['d', 'parent_obj'],
54 'pci_bridge': ['bridge.dev', 'parent_obj', 'bridge.dev.shpc', 'shpc'],
55 'pcnet': ['pci_dev', 'parent_obj'],
56 'PIIX3': ['pci_irq_levels', 'pci_irq_levels_vmstate'],
57 'piix4_pm': ['dev', 'parent_obj', 'pci0_status',
58 'acpi_pci_hotplug.acpi_pcihp_pci_status[0x0]'],
59 'rtl8139': ['dev', 'parent_obj'],
60 'qxl': ['num_surfaces', 'ssd.num_surfaces'],
61 'usb-host': ['dev', 'parent_obj'],
62 'usb-mouse': ['usb-ptr-queue', 'HIDPointerEventQueue'],
63 'usb-tablet': ['usb-ptr-queue', 'HIDPointerEventQueue'],
64 'xhci': ['pci_dev', 'parent_obj'],
65 'xio3130-express-downstream-port': ['port.br.dev',
66 'parent_obj.parent_obj.parent_obj',
67 'port.br.dev.exp.aer_log',
68 'parent_obj.parent_obj.parent_obj.exp.aer_log'],
69 'xio3130-express-upstream-port': ['br.dev', 'parent_obj.parent_obj',
70 'br.dev.exp.aer_log',
71 'parent_obj.parent_obj.exp.aer_log'],
74 if not name in changed_names:
75 return False
77 if s_field in changed_names[name] and d_field in changed_names[name]:
78 return True
80 return False
82 def get_changed_sec_name(sec):
83 # Section names can change -- see commit 292b1634 for an example.
84 changes = {
85 "ICH9 LPC": "ICH9-LPC",
88 for item in changes:
89 if item == sec:
90 return changes[item]
91 if changes[item] == sec:
92 return item
93 return ""
95 def exists_in_substruct(fields, item):
96 # Some QEMU versions moved a few fields inside a substruct. This
97 # kept the on-wire format the same. This function checks if
98 # something got shifted inside a substruct. For example, the
99 # change in commit 1f42d22233b4f3d1a2933ff30e8d6a6d9ee2d08f
101 if not "Description" in fields:
102 return False
104 if not "Fields" in fields["Description"]:
105 return False
107 substruct_fields = fields["Description"]["Fields"]
109 if substruct_fields == []:
110 return False
112 return check_fields_match(fields["Description"]["name"],
113 substruct_fields[0]["field"], item)
116 def check_fields(src_fields, dest_fields, desc, sec):
117 # This function checks for all the fields in a section. If some
118 # fields got embedded into a substruct, this function will also
119 # attempt to check inside the substruct.
121 d_iter = iter(dest_fields)
122 s_iter = iter(src_fields)
124 # Using these lists as stacks to store previous value of s_iter
125 # and d_iter, so that when time comes to exit out of a substruct,
126 # we can go back one level up and continue from where we left off.
128 s_iter_list = []
129 d_iter_list = []
131 advance_src = True
132 advance_dest = True
134 while True:
135 if advance_src:
136 try:
137 s_item = s_iter.next()
138 except StopIteration:
139 if s_iter_list == []:
140 break
142 s_iter = s_iter_list.pop()
143 continue
144 else:
145 # We want to avoid advancing just once -- when entering a
146 # dest substruct, or when exiting one.
147 advance_src = True
149 if advance_dest:
150 try:
151 d_item = d_iter.next()
152 except StopIteration:
153 if d_iter_list == []:
154 # We were not in a substruct
155 print "Section \"" + sec + "\",",
156 print "Description " + "\"" + desc + "\":",
157 print "expected field \"" + s_item["field"] + "\",",
158 print "while dest has no further fields"
159 bump_taint()
160 break
162 d_iter = d_iter_list.pop()
163 advance_src = False
164 continue
165 else:
166 advance_dest = True
168 if not check_fields_match(desc, s_item["field"], d_item["field"]):
169 # Some fields were put in substructs, keeping the
170 # on-wire format the same, but breaking static tools
171 # like this one.
173 # First, check if dest has a new substruct.
174 if exists_in_substruct(d_item, s_item["field"]):
175 # listiterators don't have a prev() function, so we
176 # have to store our current location, descend into the
177 # substruct, and ensure we come out as if nothing
178 # happened when the substruct is over.
180 # Essentially we're opening the substructs that got
181 # added which didn't change the wire format.
182 d_iter_list.append(d_iter)
183 substruct_fields = d_item["Description"]["Fields"]
184 d_iter = iter(substruct_fields)
185 advance_src = False
186 continue
188 # Next, check if src has substruct that dest removed
189 # (can happen in backward migration: 2.0 -> 1.5)
190 if exists_in_substruct(s_item, d_item["field"]):
191 s_iter_list.append(s_iter)
192 substruct_fields = s_item["Description"]["Fields"]
193 s_iter = iter(substruct_fields)
194 advance_dest = False
195 continue
197 print "Section \"" + sec + "\",",
198 print "Description \"" + desc + "\":",
199 print "expected field \"" + s_item["field"] + "\",",
200 print "got \"" + d_item["field"] + "\"; skipping rest"
201 bump_taint()
202 break
204 check_version(s_item, d_item, sec, desc)
206 if not "Description" in s_item:
207 # Check size of this field only if it's not a VMSTRUCT entry
208 check_size(s_item, d_item, sec, desc, s_item["field"])
210 check_description_in_list(s_item, d_item, sec, desc)
213 def check_subsections(src_sub, dest_sub, desc, sec):
214 for s_item in src_sub:
215 found = False
216 for d_item in dest_sub:
217 if s_item["name"] != d_item["name"]:
218 continue
220 found = True
221 check_descriptions(s_item, d_item, sec)
223 if not found:
224 print "Section \"" + sec + "\", Description \"" + desc + "\":",
225 print "Subsection \"" + s_item["name"] + "\" not found"
226 bump_taint()
229 def check_description_in_list(s_item, d_item, sec, desc):
230 if not "Description" in s_item:
231 return
233 if not "Description" in d_item:
234 print "Section \"" + sec + "\", Description \"" + desc + "\",",
235 print "Field \"" + s_item["field"] + "\": missing description"
236 bump_taint()
237 return
239 check_descriptions(s_item["Description"], d_item["Description"], sec)
242 def check_descriptions(src_desc, dest_desc, sec):
243 check_version(src_desc, dest_desc, sec, src_desc["name"])
245 if not check_fields_match(sec, src_desc["name"], dest_desc["name"]):
246 print "Section \"" + sec + "\":",
247 print "Description \"" + src_desc["name"] + "\"",
248 print "missing, got \"" + dest_desc["name"] + "\" instead; skipping"
249 bump_taint()
250 return
252 for f in src_desc:
253 if not f in dest_desc:
254 print "Section \"" + sec + "\"",
255 print "Description \"" + src_desc["name"] + "\":",
256 print "Entry \"" + f + "\" missing"
257 bump_taint()
258 continue
260 if f == 'Fields':
261 check_fields(src_desc[f], dest_desc[f], src_desc["name"], sec)
263 if f == 'Subsections':
264 check_subsections(src_desc[f], dest_desc[f], src_desc["name"], sec)
267 def check_version(s, d, sec, desc=None):
268 if s["version_id"] > d["version_id"]:
269 print "Section \"" + sec + "\"",
270 if desc:
271 print "Description \"" + desc + "\":",
272 print "version error:", s["version_id"], ">", d["version_id"]
273 bump_taint()
275 if not "minimum_version_id" in d:
276 return
278 if s["version_id"] < d["minimum_version_id"]:
279 print "Section \"" + sec + "\"",
280 if desc:
281 print "Description \"" + desc + "\":",
282 print "minimum version error:", s["version_id"], "<",
283 print d["minimum_version_id"]
284 bump_taint()
287 def check_size(s, d, sec, desc=None, field=None):
288 if s["size"] != d["size"]:
289 print "Section \"" + sec + "\"",
290 if desc:
291 print "Description \"" + desc + "\"",
292 if field:
293 print "Field \"" + field + "\"",
294 print "size mismatch:", s["size"], ",", d["size"]
295 bump_taint()
298 def check_machine_type(s, d):
299 if s["Name"] != d["Name"]:
300 print "Warning: checking incompatible machine types:",
301 print "\"" + s["Name"] + "\", \"" + d["Name"] + "\""
302 return
305 def main():
306 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."
308 parser = argparse.ArgumentParser(description=help_text)
309 parser.add_argument('-s', '--src', type=file, required=True,
310 help='json dump from src qemu')
311 parser.add_argument('-d', '--dest', type=file, required=True,
312 help='json dump from dest qemu')
313 parser.add_argument('--reverse', required=False, default=False,
314 action='store_true',
315 help='reverse the direction')
316 args = parser.parse_args()
318 src_data = json.load(args.src)
319 dest_data = json.load(args.dest)
320 args.src.close()
321 args.dest.close()
323 if args.reverse:
324 temp = src_data
325 src_data = dest_data
326 dest_data = temp
328 for sec in src_data:
329 dest_sec = sec
330 if not dest_sec in dest_data:
331 # Either the section name got changed, or the section
332 # doesn't exist in dest.
333 dest_sec = get_changed_sec_name(sec)
334 if not dest_sec in dest_data:
335 print "Section \"" + sec + "\" does not exist in dest"
336 bump_taint()
337 continue
339 s = src_data[sec]
340 d = dest_data[dest_sec]
342 if sec == "vmschkmachine":
343 check_machine_type(s, d)
344 continue
346 check_version(s, d, sec)
348 for entry in s:
349 if not entry in d:
350 print "Section \"" + sec + "\": Entry \"" + entry + "\"",
351 print "missing"
352 bump_taint()
353 continue
355 if entry == "Description":
356 check_descriptions(s[entry], d[entry], sec)
358 return taint
361 if __name__ == '__main__':
362 sys.exit(main())