Merge remote-tracking branch 'public/tags/block-pull-request' into staging
[qemu.git] / scripts / vmstate-static-checker.py
blob14a27e7f6a5b74cc42f5512e8bdb7f3f323b3c29
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 'apic': ['timer', 'timer_expiry'],
46 'e1000': ['dev', 'parent_obj'],
47 'ehci': ['dev', 'pcidev'],
48 'I440FX': ['dev', 'parent_obj'],
49 'ich9_ahci': ['card', 'parent_obj'],
50 'ich9-ahci': ['ahci', 'ich9_ahci'],
51 'ioh3420': ['PCIDevice', 'PCIEDevice'],
52 'ioh-3240-express-root-port': ['port.br.dev',
53 'parent_obj.parent_obj.parent_obj',
54 'port.br.dev.exp.aer_log',
55 'parent_obj.parent_obj.parent_obj.exp.aer_log'],
56 'cirrus_vga': ['hw_cursor_x', 'vga.hw_cursor_x',
57 'hw_cursor_y', 'vga.hw_cursor_y'],
58 'lsiscsi': ['dev', 'parent_obj'],
59 'mch': ['d', 'parent_obj'],
60 'pci_bridge': ['bridge.dev', 'parent_obj', 'bridge.dev.shpc', 'shpc'],
61 'pcnet': ['pci_dev', 'parent_obj'],
62 'PIIX3': ['pci_irq_levels', 'pci_irq_levels_vmstate'],
63 'piix4_pm': ['dev', 'parent_obj', 'pci0_status',
64 'acpi_pci_hotplug.acpi_pcihp_pci_status[0x0]',
65 'pm1a.sts', 'ar.pm1.evt.sts', 'pm1a.en', 'ar.pm1.evt.en',
66 'pm1_cnt.cnt', 'ar.pm1.cnt.cnt',
67 'tmr.timer', 'ar.tmr.timer',
68 'tmr.overflow_time', 'ar.tmr.overflow_time',
69 'gpe', 'ar.gpe'],
70 'rtl8139': ['dev', 'parent_obj'],
71 'qxl': ['num_surfaces', 'ssd.num_surfaces'],
72 'usb-ccid': ['abProtocolDataStructure', 'abProtocolDataStructure.data'],
73 'usb-host': ['dev', 'parent_obj'],
74 'usb-mouse': ['usb-ptr-queue', 'HIDPointerEventQueue'],
75 'usb-tablet': ['usb-ptr-queue', 'HIDPointerEventQueue'],
76 'vmware_vga': ['card', 'parent_obj'],
77 'vmware_vga_internal': ['depth', 'new_depth'],
78 'xhci': ['pci_dev', 'parent_obj'],
79 'x3130-upstream': ['PCIDevice', 'PCIEDevice'],
80 'xio3130-express-downstream-port': ['port.br.dev',
81 'parent_obj.parent_obj.parent_obj',
82 'port.br.dev.exp.aer_log',
83 'parent_obj.parent_obj.parent_obj.exp.aer_log'],
84 'xio3130-downstream': ['PCIDevice', 'PCIEDevice'],
85 'xio3130-express-upstream-port': ['br.dev', 'parent_obj.parent_obj',
86 'br.dev.exp.aer_log',
87 'parent_obj.parent_obj.exp.aer_log'],
90 if not name in changed_names:
91 return False
93 if s_field in changed_names[name] and d_field in changed_names[name]:
94 return True
96 return False
98 def get_changed_sec_name(sec):
99 # Section names can change -- see commit 292b1634 for an example.
100 changes = {
101 "ICH9 LPC": "ICH9-LPC",
102 "e1000-82540em": "e1000",
105 for item in changes:
106 if item == sec:
107 return changes[item]
108 if changes[item] == sec:
109 return item
110 return ""
112 def exists_in_substruct(fields, item):
113 # Some QEMU versions moved a few fields inside a substruct. This
114 # kept the on-wire format the same. This function checks if
115 # something got shifted inside a substruct. For example, the
116 # change in commit 1f42d22233b4f3d1a2933ff30e8d6a6d9ee2d08f
118 if not "Description" in fields:
119 return False
121 if not "Fields" in fields["Description"]:
122 return False
124 substruct_fields = fields["Description"]["Fields"]
126 if substruct_fields == []:
127 return False
129 return check_fields_match(fields["Description"]["name"],
130 substruct_fields[0]["field"], item)
133 def check_fields(src_fields, dest_fields, desc, sec):
134 # This function checks for all the fields in a section. If some
135 # fields got embedded into a substruct, this function will also
136 # attempt to check inside the substruct.
138 d_iter = iter(dest_fields)
139 s_iter = iter(src_fields)
141 # Using these lists as stacks to store previous value of s_iter
142 # and d_iter, so that when time comes to exit out of a substruct,
143 # we can go back one level up and continue from where we left off.
145 s_iter_list = []
146 d_iter_list = []
148 advance_src = True
149 advance_dest = True
150 unused_count = 0
152 while True:
153 if advance_src:
154 try:
155 s_item = s_iter.next()
156 except StopIteration:
157 if s_iter_list == []:
158 break
160 s_iter = s_iter_list.pop()
161 continue
162 else:
163 if unused_count == 0:
164 # We want to avoid advancing just once -- when entering a
165 # dest substruct, or when exiting one.
166 advance_src = True
168 if advance_dest:
169 try:
170 d_item = d_iter.next()
171 except StopIteration:
172 if d_iter_list == []:
173 # We were not in a substruct
174 print "Section \"" + sec + "\",",
175 print "Description " + "\"" + desc + "\":",
176 print "expected field \"" + s_item["field"] + "\",",
177 print "while dest has no further fields"
178 bump_taint()
179 break
181 d_iter = d_iter_list.pop()
182 advance_src = False
183 continue
184 else:
185 if unused_count == 0:
186 advance_dest = True
188 if unused_count != 0:
189 if advance_dest == False:
190 unused_count = unused_count - s_item["size"]
191 if unused_count == 0:
192 advance_dest = True
193 continue
194 if unused_count < 0:
195 print "Section \"" + sec + "\",",
196 print "Description \"" + desc + "\":",
197 print "unused size mismatch near \"",
198 print s_item["field"] + "\""
199 bump_taint()
200 break
201 continue
203 if advance_src == False:
204 unused_count = unused_count - d_item["size"]
205 if unused_count == 0:
206 advance_src = True
207 continue
208 if unused_count < 0:
209 print "Section \"" + sec + "\",",
210 print "Description \"" + desc + "\":",
211 print "unused size mismatch near \"",
212 print d_item["field"] + "\""
213 bump_taint()
214 break
215 continue
217 if not check_fields_match(desc, s_item["field"], d_item["field"]):
218 # Some fields were put in substructs, keeping the
219 # on-wire format the same, but breaking static tools
220 # like this one.
222 # First, check if dest has a new substruct.
223 if exists_in_substruct(d_item, s_item["field"]):
224 # listiterators don't have a prev() function, so we
225 # have to store our current location, descend into the
226 # substruct, and ensure we come out as if nothing
227 # happened when the substruct is over.
229 # Essentially we're opening the substructs that got
230 # added which didn't change the wire format.
231 d_iter_list.append(d_iter)
232 substruct_fields = d_item["Description"]["Fields"]
233 d_iter = iter(substruct_fields)
234 advance_src = False
235 continue
237 # Next, check if src has substruct that dest removed
238 # (can happen in backward migration: 2.0 -> 1.5)
239 if exists_in_substruct(s_item, d_item["field"]):
240 s_iter_list.append(s_iter)
241 substruct_fields = s_item["Description"]["Fields"]
242 s_iter = iter(substruct_fields)
243 advance_dest = False
244 continue
246 if s_item["field"] == "unused" or d_item["field"] == "unused":
247 if s_item["size"] == d_item["size"]:
248 continue
250 if d_item["field"] == "unused":
251 advance_dest = False
252 unused_count = d_item["size"] - s_item["size"]
253 continue
255 if s_item["field"] == "unused":
256 advance_src = False
257 unused_count = s_item["size"] - d_item["size"]
258 continue
260 print "Section \"" + sec + "\",",
261 print "Description \"" + desc + "\":",
262 print "expected field \"" + s_item["field"] + "\",",
263 print "got \"" + d_item["field"] + "\"; skipping rest"
264 bump_taint()
265 break
267 check_version(s_item, d_item, sec, desc)
269 if not "Description" in s_item:
270 # Check size of this field only if it's not a VMSTRUCT entry
271 check_size(s_item, d_item, sec, desc, s_item["field"])
273 check_description_in_list(s_item, d_item, sec, desc)
276 def check_subsections(src_sub, dest_sub, desc, sec):
277 for s_item in src_sub:
278 found = False
279 for d_item in dest_sub:
280 if s_item["name"] != d_item["name"]:
281 continue
283 found = True
284 check_descriptions(s_item, d_item, sec)
286 if not found:
287 print "Section \"" + sec + "\", Description \"" + desc + "\":",
288 print "Subsection \"" + s_item["name"] + "\" not found"
289 bump_taint()
292 def check_description_in_list(s_item, d_item, sec, desc):
293 if not "Description" in s_item:
294 return
296 if not "Description" in d_item:
297 print "Section \"" + sec + "\", Description \"" + desc + "\",",
298 print "Field \"" + s_item["field"] + "\": missing description"
299 bump_taint()
300 return
302 check_descriptions(s_item["Description"], d_item["Description"], sec)
305 def check_descriptions(src_desc, dest_desc, sec):
306 check_version(src_desc, dest_desc, sec, src_desc["name"])
308 if not check_fields_match(sec, src_desc["name"], dest_desc["name"]):
309 print "Section \"" + sec + "\":",
310 print "Description \"" + src_desc["name"] + "\"",
311 print "missing, got \"" + dest_desc["name"] + "\" instead; skipping"
312 bump_taint()
313 return
315 for f in src_desc:
316 if not f in dest_desc:
317 print "Section \"" + sec + "\"",
318 print "Description \"" + src_desc["name"] + "\":",
319 print "Entry \"" + f + "\" missing"
320 bump_taint()
321 continue
323 if f == 'Fields':
324 check_fields(src_desc[f], dest_desc[f], src_desc["name"], sec)
326 if f == 'Subsections':
327 check_subsections(src_desc[f], dest_desc[f], src_desc["name"], sec)
330 def check_version(s, d, sec, desc=None):
331 if s["version_id"] > d["version_id"]:
332 print "Section \"" + sec + "\"",
333 if desc:
334 print "Description \"" + desc + "\":",
335 print "version error:", s["version_id"], ">", d["version_id"]
336 bump_taint()
338 if not "minimum_version_id" in d:
339 return
341 if s["version_id"] < d["minimum_version_id"]:
342 print "Section \"" + sec + "\"",
343 if desc:
344 print "Description \"" + desc + "\":",
345 print "minimum version error:", s["version_id"], "<",
346 print d["minimum_version_id"]
347 bump_taint()
350 def check_size(s, d, sec, desc=None, field=None):
351 if s["size"] != d["size"]:
352 print "Section \"" + sec + "\"",
353 if desc:
354 print "Description \"" + desc + "\"",
355 if field:
356 print "Field \"" + field + "\"",
357 print "size mismatch:", s["size"], ",", d["size"]
358 bump_taint()
361 def check_machine_type(s, d):
362 if s["Name"] != d["Name"]:
363 print "Warning: checking incompatible machine types:",
364 print "\"" + s["Name"] + "\", \"" + d["Name"] + "\""
365 return
368 def main():
369 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."
371 parser = argparse.ArgumentParser(description=help_text)
372 parser.add_argument('-s', '--src', type=file, required=True,
373 help='json dump from src qemu')
374 parser.add_argument('-d', '--dest', type=file, required=True,
375 help='json dump from dest qemu')
376 parser.add_argument('--reverse', required=False, default=False,
377 action='store_true',
378 help='reverse the direction')
379 args = parser.parse_args()
381 src_data = json.load(args.src)
382 dest_data = json.load(args.dest)
383 args.src.close()
384 args.dest.close()
386 if args.reverse:
387 temp = src_data
388 src_data = dest_data
389 dest_data = temp
391 for sec in src_data:
392 dest_sec = sec
393 if not dest_sec in dest_data:
394 # Either the section name got changed, or the section
395 # doesn't exist in dest.
396 dest_sec = get_changed_sec_name(sec)
397 if not dest_sec in dest_data:
398 print "Section \"" + sec + "\" does not exist in dest"
399 bump_taint()
400 continue
402 s = src_data[sec]
403 d = dest_data[dest_sec]
405 if sec == "vmschkmachine":
406 check_machine_type(s, d)
407 continue
409 check_version(s, d, sec)
411 for entry in s:
412 if not entry in d:
413 print "Section \"" + sec + "\": Entry \"" + entry + "\"",
414 print "missing"
415 bump_taint()
416 continue
418 if entry == "Description":
419 check_descriptions(s[entry], d[entry], sec)
421 return taint
424 if __name__ == '__main__':
425 sys.exit(main())